JVM的垃圾回收

垃圾回收主要关注的是JVM内存中的堆和方法区部分将内存中没有任何指针指向的对象进行回收,释放内存,避免内存溢出。

标记阶段

判断对象是否存活的算法一般有两种,引用计数算法可达性分析算法

  1. 引用计数法:为每个对象保存一个引用计数器,对象被引用和引用失效时进行增减,归零时表示可以进行回收,但是无法处理循环引用的情况。
  2. Java中采用的是可达性分析算法:是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达,如果对象没有任何引用链,则不可达,为垃圾对象。

清除阶段

jvm的垃圾回收是根据不同年代的特点执行不同的垃圾回收算法,最基本的垃圾回收算法有三种,标记-清除算法、复制算法、标记-整理算法

  • 标记-清除算法:从引用根节点开始遍历,标记被引用的可达对象,清除未标记的对象
    缺点:效率不高,进行GC时需要停止整个程序,清理出的内存不连续,需要维护空闲列表
  • 复制算法:将内存空间分为两块,垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
    缺点:需要的空间和开销较大。 常用于存活对象少,垃圾对象多的新生代中。
  • 标记-整理算法:从根节点开始标记所有被引用对象,再将所有的存活对象压缩到内存的一端,按顺序排放,然后清理边界外所有的空间
  • 分代收集算法(目前主要使用的算法)新生代对象生命周期短、存活率低,回收频繁,使用复制算法,清理效率高。老年代区域较大,对象生命周期长、存活率高,回收不及年轻代频繁,一般是由标记-清除或者是标记-清除与标记-整理的混合实现。
  • 增量收集算法:基础仍是传统的标记-清除和复制算法,通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。
  • 分区算法:将整个堆空间划分成连续的不同小区间,每一个小区间都独立使用,独立回收,根据目标的停顿时间,每次合理地回收若干个小区间

一次完整的流程

  • Java堆里面有新生代和老年代
  • 新生代里又有Eden区和S0、S1区
  • Eden 区的空间满了, Java 虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor 区
  • 大对象(需要大量连续内存空间的 Java 对象,如那种很长的字符串)直接进入老年代
  • 如果对象在 Eden 出生,并经过第一次 Minor GC 后仍然存活,并且被 Survivor容纳的话,年龄设为 1,每熬过一次 Minor GC,年龄+1若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态
  • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行 Full GC,FullGC 清理整个内存堆包括年轻代和老年代
  • Major GC 发生在老年代的 GC,清理老年区,经常会伴随至少一次 Minor GC,比 Minor GC 慢 10 倍以上

垃圾回收器

新生代的垃圾回收器

Serial:单线程的收集器,收集垃圾时,必须 stop the world,使用复制算法
ParNew:Serial 收集器的多线程版本
Parallel Scavenger:新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量

老年代的垃圾回收器

Serial Old:是 Serial 收集器的老年代版本,单线程收集器,使用标记-整理算法
Parallel Old:Parallel Scavenge 收集器的老年代版本,使用多线程标记-整理算法
CMS回收器:是以实现最短 STW 时间为目标的收集器,采用的是标记清除法,主要有四个步骤,初始标记GC Root能关联的对象,并发标记进行GC Root Tracing,重新标记为了修复和用户进程并发导致的一些对象的变动,最后进行清理。缺点是需要占用一定的CPU资源、无法处理浮动垃圾、产生了碎片空间
G1回收器:面向服务端的回收器,主要是标记-整理算法,两个区域之间用的是复制算法,比CMS整理空间更快,性能更好,各代储存的地址不连续,需要的GC停顿时间更好预测,不会产生碎片空间