一文读懂JVM中的垃圾回收器

  

这篇文章给大家介绍一文读懂JVM中的垃圾回收器,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

JVM的GC经过多年的发展,大家对Minor GC、major GC的理解并不完全一致,所以我不打算在本文中使用这个概念。我把GC大概分为一下4类:

  • Young GC:只是负责回收年轻代对象的GC;
  • Old GC:只是负责回收老年代对象的GC;
  • Full GC:回收整个堆的对象,包括年轻代、老年代、持久带;
  • Mixed GC:回收年轻代和部分老年代的GC (G1);

因为笔者目前使用G1还是比较少的,所以本文不打算将G1。

垃圾回收器算法

目前主流垃圾回收器都采用的是可达性分析算法来判断对象是否已经存活,不使用引用计数算法判断对象时候存活的原因在于该算法很难解决相互引用的问题。

标记-清除算法(Mark-Sweep)

标记-清除算法由标记阶段和清除阶段构成。标记阶段是把所有活着的对象都做上标记的阶段;清除阶段是把那些没有标记的对象,也就是非活动对象回收的阶段。通过这两个阶段,就可以令不能利用的内存空间重新得到利用。

从标记-清除算法我们可以看出,该算法不涉及对象移动,但是可能会产生内存碎片化问题。空间碎片太高可能会导致程序运行时需要分配较大内存时候,无法找到足够的连续内存,需要其他垃圾回收帮助回收内存。

复制算法(Copying)

复制算法内存空间分为两块区域:From、to,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

上面那种复制算法有一半的空间是浪费的。所以在Java新生代把内存区域分为Eden空间、from、to空间3个部分,from和to空间也称为survivor 空间,用于存放未被回收的对象。对象开始都是Eden生成;当回收时,将Eden和from中存活的对象移动到to区域中。

复制算法存在空间浪费的情况,始终都要保持一个Survivor是空闲的,并且在GC的时候要是存活对象大小超过了Survivor中的大小,就需要另外的策略存储存活对象。

目前open JDK新生代回收策略就是采用的复制算法,其中Eden和Survivor的默认配置为8:1

标记-压缩算法(Mark-Compact)

标记-压缩算法由标记阶段和压缩阶段构成。标记阶段标记-清除算法中的标记阶段完全一样,压缩阶段是让所有存活的对象向一端移动。这样空闲内存都在另外一端,属于连续空间,不存在内存碎片化问题,但是会产生对象移动。

分代算法(Generational GC)

根据对象的不同生命周期分别管理, JVM 中将对象分为我们熟悉的新生代、老年代和永久代分别管理。这样做的好处就是可以根据不同类型对象进行不同策略的管理,例如新生代中对象更新速度快,就会使用效率较高的复制算法。老年代中内存空间相对分配较大,而且时效性不如新生代强,就会常常使用Mark-Sweep-Compact (标记-清除-压缩)算法。

各种算法性能比较

一文读懂JVM中的垃圾回收器

常见的垃圾回收器

垃圾回收器分类

总体上可以把Java的垃圾回收器分为3类:

  • 串行垃圾回收器(Serial Garbage Collector)
  • 并行垃圾回收器(Parallel Garbage Collector)
  • 并发标记扫描垃圾回收器(CMS Garbage Collector)

Java垃圾回收器主要有6种,各自优缺点以及组合关系如下:

一文读懂JVM中的垃圾回收器

其中的连线表示young gc和old gc可以搭配使用

一文读懂JVM中的垃圾回收器

垃圾回收器选择策略:

  • 客户端程序:Serial + Serial Old;
  • 吞吐率优先的服务端程序(比如:计算密集型):Parallel Scavenge + Parallel Old;
  • 响应时间优先的服务端程序:ParNew + CMS。

目前很大一部分的Java应用都集中在互联网的服务器端,这类应用尤其关系服务的响应时间,希望应用暂停时间更短,所以基本上使用的都是ParNew + CMS,这也是我司默认使用的配置。

一文读懂JVM中的垃圾回收器