JVM垃圾回收

JVM垃圾收集器
一、垃圾收集器的分类 1.1 Serial GC
出现的最早,比较古老,最大的特点就是垃圾收集策略是单线程的,采用的算法则是“标记-整理”,并且在收集的过程中会进行stop-the-word,实现比较简单,是Client模式下的默认选项。
1.2 ParNew GC
针对新生代的垃圾收集实现的,是SerialGC的多线程版本,经常配合老年代的CMS一起工作。
1.3 CMS GC
基于“标记-清除”算法,设计目标是为了减少停顿时间,但是可能会存在内存碎片化的问题,系统运行时间较长时势必会引发Full GC。由于其是并发的,势必也会占用更多的资源,并和用户线程争抢。
1.4 Parallel GC
在Java8中是server模式下的默认选项,算法和Serial GC类似,其特点是新生代和老年代的垃圾收集都是并行的,是吞吐量优先的GC,在服务器环境下更加高效。
1.5 G1 GC
在Java9中是server模式下的默认选项,是一种兼顾吞吐量和停顿时间的GC实现,其采用的是“标记-整理”算法,能有效地避免内存碎片。
1.6 总结

  • 第一代垃圾收集器SerialGC比较简单,单线程资源占用小,但是会STW,仅在Client模式下使用;
  • 第二代垃圾收集器ParNewGC负责新生代,实现了多线程,效率有所提升;CMSGC负责老年代,采用的是标记清除算法,会造成内存碎片,但是优势在于可以合理地设置停顿;
  • 第三代垃圾收集器ParallelGC是Java8的默认选项,可以并行对新生代和老年代进行工作,是吞吐量优先的;
  • 最新的是G1GC,是Java9的默认选项,兼顾吞吐量和停顿的设置,采用标记整理算法,可以避免CMS的内存碎片问题。
二、垃圾收集器的工作原理 2.1 哪些对象可以被回收?
  • 【JVM垃圾回收】引用计数法
    每个对象有一个引用计数器,当计数器为0的时候,说明没有地方再使用它,可以标记为垃圾等待回收,但是最大的缺点就是无法解决循环引用的问题。
  • 可达性分析
    GC Roots不可达的对象,就可以被标记为待回收的垃圾,一般而言,Java虚拟机栈和本地方法栈中正在引用的对象、静态属性引用的对象和常量会被当做是GC Roots。
    这一点其实很好理解,栈中存放的是栈帧,栈帧代表当前一个个正在运行或者等待运行的代码,它们引用的对象肯定是还需要使用的。
2.2 垃圾收集算法有哪些?
  • 标记-复制
    在新生代,每隔一段时间就会将Eden区活着的对象copy到Survivor-To区域,剩下的对象都是要回收的。这种算法比较简单,仅适用于新生代。
  • 标记-清除
    先对所有区域进行标记,然后再停顿进行清除操作,这种算法有两个缺点,其一是容易产生内存碎片;其二是当堆比较大的时候,暂停的时间可能无法接受。
  • 标记-整理
    和标记-清除算法类似,只不过多一个步骤,在清理过程中会将对象移动,确保占用连续的内存空间,避免出现内存碎片。
2.3 垃圾收集的过程
  • Java新创建的对象都是被分配在Eden区域的(超大对象除外,会被直接分配到老年代),当Eden区域的空间占用达到一定的阈值,会触发Minor GC,即将Eden区域中还存活的对象复制到Survivor-From区域,同时年龄标记为1,而其它对象则会被回收清理。
  • 经过上一个步骤,Eden区域就会空下来,直至等到下一次Minor GC,此时会把Eden区域中存活的对象和Survivor-From区域存活的对象复制到Survivor-To区域,并且所有对象的年龄加1;同时from区域清空,并交换from和to指针,保证下次复制时,to区域是空的;
  • 如上步骤会发生很多次,直至某一次,Survivor-To区域中的对象年龄达到某个阈值,那么这样的对象就会被挪到老年代,并年龄加1,称之为晋升;
  • 老年代GC不同于新生代,合适的算法会进行标记整理操作,防止内存碎片的发生,老年代的GC通常被称为Major GC。
  • 当老年代中空间不足的时候,就会触发Full GC,此时就会停止所有非垃圾回收线程,需要尽量避免。

    推荐阅读