问:对象判定为垃圾的标准?
没有被其他对象引用
问:对象判定为垃圾的算法?
引用计数算法
- 通过判断对象的引用数量来判断对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用-1
- 任何引用计数为0的对象实例可以当垃圾收集的
优点:执行效率高,程序受影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏memory leak
可达性分析算法
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
可以作为GC Root的对象
- 虚拟机栈中的引用对象(栈帧中的本地变量表)
- 方法区中的常量引用对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 活跃线程的引用对象
问:谈谈你了解的垃圾回收算法?
标记-清除算法(mark and sweep)
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清楚:对堆内存从头到尾进行线性遍历,回收不可达对象内存,并把原来标记为可达的标记清除掉
优点:效率高,因为不需要对对象的移动操作。
缺点:碎片化,标记清除之后会留下大量不连续的内存空间,碎片太多可能会导致以后程序运行过程中无法提供连续内存,而不得不进行另一次垃圾回收
复制算法(Copying)
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象从对象面复制到空闲面
- 将对象面所有对象内存清除
优点:
- 解决了碎片化的问题 (每次复制到空闲面的对象都是连续排放的)
- 顺序分配内存,简单高效 (每次直接清理一半的内存空间,所以简单高效)
- 适用于对象存活率低的场景,比如年轻代(现在成熟的商业虚拟机中都采用这种算法回收年轻代,因为年轻代一般只有10%的对象存活,所以使用这种算法效率还不错)
缺点:在应对对象存活率较高时就有些力不从心了,因为有较多的复制操作,效率将会变低。而且不想浪费额外的50%空间,就需要更多的空间进行担保,因为需要应对所有对象都100%存活的极端情况,所以老年代一般不能选用这种算法。
标记-整理算法(Compacting)
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:移动所有存活的对象,且按照内存地址次序一次排序,然后将末端内存地址以后的内存全部回收
优点:解决了内存碎片化的问题,清理之后内存是连续的,也不用设置两块内存互换,适用于存活率较高的场景
缺点:需要移动标记存活的对象,成本较高
分代收集算法(Generational Collector) 现在主流的GC算法
- 垃圾回收算法的组合拳
- 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
- 目的:提高JVM的回收效率
JDK8之后没有永久代,但是年轻代、老年代都保留了下来,年轻代使用的是复制算法,老年代使用的标记整理算法
问:分代收集算法的GC分几种?
- Minor GC (发生在年轻代中的垃圾收集工作,使用复制算法。年轻代是所有java对象出生的地方,新对象一般存活率较低,所以MinorGC比较频繁)
- Full GC
年轻代:
- Eden区(就是伊甸园,代表这人类的起源,新对象存放的区域,如果eden区放不下新对象,也有可能会放在survivor区甚至是老年代中)
- 两个Survivor区(幸存者区,from区和to区,这两个区也不是固定的,会随着垃圾回收而相互转换)
默认是8:1:1的比例分配,垃圾清理时会将eden和from区的存活对象一次性复制到to区,to区不够用的时候需要老年代做担保
年轻代垃圾回收的过程演示:
每次存活就会标记+1,默认是15岁,也可以设置-XX:MaxTenuringThreshold设置最大年龄限制,到达限制就会进入老年代,当然如果eden或者survivor装不下也会进入老年代
对象如何晋升为老年代:
- 经历一定Minor次数依然存活的对象
- Survivor区放不下的对象
- 新生成的大对象(可以设置-XX:+PretenuerSizeThreshold设置大对象大小,超过这个大小的大对象会直接放入老年代)
常用的调优参数:
- -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
- -XX:NewRatio:老年代和年轻代内存大小比例,比如2表示老年代是年轻代的2倍
老年代:存放生命周期较长的对象
- 标记-清理算法
- 标记-整理算法
一般会伴随着新生代的回收及整个堆的回收(FullGC和MajorGC,FullGC比MinorGC慢,但执行频率低)
问:出发FullGC的条件?
- 老年代空间不足(最好不要创建太大对象)
- 永久代空间不足(JDK7以前的版本,这也是JDK8使用元空间替代永久代的原因,降低了FullGC的频率)
- CMS GC时出现promotion failed ,concurrent mode failure(注意日志里是否出现这两个情况,这两种情况很容易触发FullGC)
- Minor GC 晋升到老年代的平均大小大于老年代剩余空间(hotspot在进行MinorGC做了个统计,如果晋升到老年代的平均大小大于老年代剩余空间就直接触发Full GC)
- 调用System.gc()(这只是程序提醒虚拟机,码农希望这里进行一下回收,但回不回收还是要看JVM)
- 使用RMI来进行RPC或者管理JDK应用,默认每小时执行一次FullGC