JVM垃圾回收原理学习总结
背景
最近复习学习了JVM垃圾回收原理,尝试用自己的白话描述一遍
总结
Java语言的优势之一在于JVM提供了了java中对象的自动回收机制,使得开发人员无需关心对象的回收,在JVM中常见的垃圾回收算法有三种,标记-清除、标记-复制、标记-整理。 每种算法首先都需要对存活对象进行标记,然后将未标记的对象进行清理,从而达到垃圾回收的目的。而常见的标记算法又有两种,一种是可达性分析,另一种是引用计数法,可达性分析的核心思想是先规定一些根节点,然后以根节点出发,查看根节点都被哪些对象所引用,然后再查看这些引用对象或者其属性对象是否被引用,被引用的对象说明是存活对象,反之则是垃圾对象,需要被清理;这个过程会使用到三色标记法,其中的三色分别为黑色、灰色、白色,被标记为黑色的对象说明对象本身及对象中属性是有被引用的,而灰色表示对象本身被标记,而子对象还没有完成检测。白色表示此对象是没有检测到的对象,在使用三色标记法时,如果是并发进行的,则可能出现多标或者漏标的问题,此时需要用到读写屏障来解决,读写屏障的思想并不是我们认为的缓存一致性中的读写屏障,而是更像是spring中的拦截器一样的思想,在标记时进行拦截。这大概是可达性分析的一个过程,可达性分析的缺点在于,只是对对象进行标记,而不能立即回收,需要等到一定阈值之后,由垃圾回收器开始执行,此过程会导致STW,影响用户体验。 另一种标记算法叫引用计数法,核心原理是每个对象需要有个计数器计算该对象被引用的次数,引用计数法的使用没有可达性分析多,主要原因在于其可能产生循环依赖的问题,当两个对象互相引用,但是这两个对象并没有被其他任何对象引用时,其实这两个对象属于垃圾对象,但是因为被引用数量不为1,而不会被回收。因此其实现的复杂度较高且因为会频繁的技术而可能占用cpu性能。
知道了标记的过程后,来说说垃圾回收常用的算法之一:
标记-清除:实现过程是将存活对象进行标记后,将没有被标记的对象进行清除。优点实现简单,缺点是会产生内存碎片,随着清理次数越来越多,可能会导致无法分配大对象而出现OOM。
标记-复制: 常见的做法是将内存空间一分为二,对象只会放到其中一半的空间,GC时将标记存活的对象复制到另一半空白的空间,然后一次性清空原来的空间中的所有对象,优点是不会出现内存碎片的问题,缺点是空间利用率低。
标记-整理: 同样为了解决内存碎片的问题以及避免空间使用率不高的问题,此算法会将标记存活的对象移动到空间的一端,此算法优点是避免产生内存碎片以及提高了空间利用率,缺点是时间复杂度会提升,STW的时间会长。
分代收集理论: 因对象的生命周期存在较大差异,有些对象生命周期短,随着方法的结束而消亡,有些对象存活周期长,可能是整个程序的生命周期。 因此针对不同生命周期的对象采用不同的收集策略是很有必要的,能大幅减少full GC的次数,从而减少STW的时间。 JVM中一般将内存区域分为2代,新生代和老年代,新生代中又分为了3个区,分别是Eden区和两个s区(s0,s1)。新创建的对象会先出生在eden区,eden区满了后会触发minor GC,将标记存活的对象复制到s0区,然后清空eden区,下次eden区满了之后会再次进行eden区和s0区的minor GC,会将两个区标记存活的对象放到s1区,然后清除eden区和s0区,因此s0和s1区交替使用,周而复始,在复制对象时会对存活的对象进行计数,当对象复制的次数达到一定的阈值时会进行晋升,将对象移动到老年代中。 老年代满了时会进行major GC,major GC是对整个老年代进行垃圾回收。 full GC通常在对象晋升失败,老年代空间满时触发,full GC触发时会对新生代和老年代以及元空间进行垃圾回收,STW时间会比较长。full GC具体的触发条件和垃圾回收器的实现有关。