「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」
垃圾的回收
JVM本身自带垃圾回收机制,一个后台自动运行的线程
思考题 方法区内会不会进行垃圾回收? 会! 不过有条件
- 首先该类的所有实例对象都已经从Java堆内存里被回收
- 其次,加载这个类的ClassLoader已经被回收掉
- 最后,对该类的Class对象没有任何引用
满足三个条件就可以回收了 / 每个线程都有Java虚拟机栈,里面也有很多局部变量等,这个虚拟机栈需要回收吗? 不会回收,出栈即清空数据
什么样的对象需要回收? -- 垃圾 怎样才能算为垃圾对象? -- 没有任何一个局部变量、静态变量、常量指向它 JVM就会定期清理这些垃圾对象
JVM的分代模型
年轻代(新生代)
存放着很快被回收的对象 大部分的正常对象,都是优先在新生代分配内存的,而对象躲过10多次(15次)垃圾回收,就能成为一个老年人了,会被认为是一个长期存活的对象,就转移到老年代中。
一旦新生代内存空间不够时,就会引起 Minor GC 回收垃圾
进入老年代的机会
- 躲过15次GC之后进入老年代 (-XX:MaxTenuringThreshold 设置进入老年代的年龄)
- 动态对象年龄判断 大致规则,在Survivor区域内,一批对象的总大小大于Survivor区域的内存大小50%时,就会把大于这批对象年龄最大的对象移到老年代中去。一批对象(年龄1+年龄2+年龄n > 50%), 年龄> n 的移动到老年代。
- 大对象直接进入老年代 (-XX:pretenureSizeThreshold 设置进入老年代的字节数)
- Minor GC后存活对象总内存大小 > Survivor内存大小,移到老年代中
- 老年代空间分配担保规则
预防Survivor存活下来的对象大于老年代的可用内存空间
步骤一:看“-XX:-HandlePromotionFailure”的参数是否设置,判断老年代大小是否大于新生代Minor GC 全部存活的对象大小,如果老年代大小小于了,下一步
步骤二:如果设置了,判断之前每一次MinorGC后进入老年代的平均大小(假如平均大小为10MB,而老年代可用内存>10MB);
步骤三:就有下面三种可能
| 第一种(Minor GC 过后,剩余存活对象大小 < Survivor 可用内存大小) | 直接存进Survivor区 |
| 第二种(老年代可用内存大小 > Minor GC 过后,剩余存活对象大小 > Survivor 可用内存大小) | 直接存进老年代 |
| 第三种(老年代可用内存大小 < Minor GC 过后,剩余存活对象大小 ) | 引发老年代 Full GC |
Full GC 过后还不够空间,会导致 “OOM” 内存溢出
老年代 存放着长期存在的对象
永久代(元空间) 就是上面的方法区,存放一些类信息
为什么要分成年轻代和老年代
年轻代对象回收频率很高,创建之后很快就要被回收了,而老年代里的对象,他们的特点是需要长期存在,要用不同的回收算法来处理。
回收
新生代内存的垃圾回收-> "Minor GC"("Young GC")
只要当前实例对象有被GC Roots(方法的局部变量,静态变量)引用了,就不会回收
JVM中使用了一种可达性分析算法来判断,分析当前实例对象有没有GC Roots
e.g.
class world{
void say(){ Systemt.out.println("Hello world!"); }
}
class test{
public static void main(String[] args) {
sayHello();
}
public void sayHello(){
world w = new world();
w.say();
}
}
如图所示,当sayHello这个栈帧使用中的时候,它引用着world的实例对象。那么w这个局部变量就是GC Roots。
不同的引用类型
强引用、软引用、弱引用、虚引用
| 引用类型 | 回收时 |
|---|---|
| 强引用 | 有GC Roots引用着,不会被回收 |
| 软引用 | 有GC Roots引用着,而内存不够用,就会被回收 |
| 弱引用 | 引用着相当于没引用,必被回收 |