天天看点

HotSpot虚拟机垃圾收集器-理论篇

java的对象实例存放在堆里,java的对象无需程序员管理,而是由垃圾收集器(Garbage Collection 简称GC)自动管理。

一般来说,大部分对象都是朝生夕灭的,而熬过越多次垃圾收集过程的对象就越难以消亡。

据此,一般垃圾收集器会根据回收对象的年龄(经历过垃圾收集过程的次数),将自身管理的堆分为新生代(Young Generation)和老年代(Old Generation)。每次回收优先回收新生代,以便快速回收大量的可用空间。

基础垃圾收集算法有三种:

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

即扫描内存区域,标记所有需要回收的对象,统一回收。这种算法有两个缺点,一个是如果内存中大部分的对象都是要回收的,效率会比较低,另一个是多次清除后,会产生很多不连续的内存碎片。碎片太多之后,分配稍大一点的对象都会触发垃圾收集。

标记-复制算法(Semispace Copying)

考虑90%以上的对象都是朝生夕灭的,都熬不过第一轮垃圾回收。可以将新生代分为三块,一块Eden,两块Survivor,默认比例8:1:1。

分配对象时只使用Eden和一块Survivor,垃圾回收时,扫描这两块内存,将存活的对象复制到另一块Survivor中,然后直接清理调Eden和已使用的那块Survivor空间。

复制到未使用的Survivor中的存活对象,在熬过特定次数后,再转移到老年代中。

标记-整理算法(Mark-Compact)

在老年代中,大部分对象都是存活的,根据这个特征,可以将所有的存款对象向内存空间一端移动,然后直接清理掉边界以外的内存。

由于移动存活对象必须更新所有引用这些对象的地方,一般来说这时必须要全程暂停用户应用程序。这一点在当前追求快速响应的互联网时代显然是一般系统无法忍受的。

基于HotSpot虚拟机的垃圾收集器有:

  • 新生代收集器 Minor GC:

Serial收集器

单线程的垃圾收集器,采用标记复制算法,整个GC过程必须全程停止用户线程。适用于堆内存较小的场景,客户端模式下的默认新生代收集器

ParNew收集器 Serial的多线程版,激活CMS收集器后的默认新生代收集器,在多核处理器系统中可以高效利率系统资源

Parallel Scavenge收集器

可以通过-XX:MaxGCPauseMillis设置最大停顿时间,或通过-XX:GCTimeRatio设置吞吐量大小,吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集运行时间)。

停顿时间设置适用于需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则适合后台运算而不需要太多交互的任务。

该收集器还提供了一个自适应的选项,-XX:+UseAdaptiveSizePolicy,使用该参数后,系统会根据用户设置的最大停顿时间或最大吞吐量,自动调节新生代大小、Eden与Survivor的比例,晋升老年代的大小等参数对虚拟机进行优化。

  • 老年代收集器 Major GC:

Serial Old收集器

Serial收集器的老年代版本,单线程。CMS失败的后备预案。

Paralle Old收集器

Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。 JDK6之后开始提供。

Concurrent Mark Sweep收集器

基于标记-清除算法的并发低停顿收集器。收集过程分为四步:初始标记、并发标记、重新标记、并发清除,仅在初始标记阶段需要停顿用户线程,其他三步都可以与用户线程同时进行。

该处理器有三个缺点:

1. 对于四核以下的处理器,会由于占用处理器资源过多,而导致用户程序执行速度大幅降低

2. 由于收集过程与用户线程并发进行,所以在老年代中必须预留一定空间供并发收集时的应用程序运作使用。

预留的空间不够并发的用户程序使用时,会出现并发失败,这时虚拟机会启用SerailOld对老年代重新进行垃圾收集,这次停顿时间就会很长。

预留空间的比例可以通过-XX:CMSInitiatingOccupancyFraction来设置,设置之后,一旦老年代的使用比例达到阈值,就会触发一次CMS GC。

预留比例设置太小,会导致GC过于频繁,过大又会导致大量的并发失败,所以需要根据生产环境的情况权衡设置。

3. 标记-清除算法会产生大量空间碎片,当碎片过多导致大对象无法分配时,也会触发FullGC,出现长时间的停顿

  • 混合收集器

Garbage First收集器

简称G1收集器

不再划分固定的分代区域,而是把连续的Java堆划分为多个大小相同的独立区域,每个Region可以根据需要,扮演Eden、Survivor或Old Generation。

对于超过Region容量一般的对象,G1判定为大对象,会分出一类特殊的Humongous区域来存放,超过一个Region大小的对象,会被存放在连续的N个Humongous Region中。Humongous区域大多数时候会被当做老年代来处理。

对于所有的Region,G1维护一个优先级列表,这个优先级会根据各个Region里面垃圾堆积的价值大小来判定,价值即回收所获得的空间及回收所需时间。

每次回收时,G1会根据用户设定的允许停顿时间,优先回收那些优先级最高的,价值最大的区域,这样就能快速收回大量可用空间。

停顿时间通过-XX:MaxGCPauseMillis设定,默认200ms。该值如果设置太小,会导致每次收集到的内存都很少,收集器的速度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积,最终占满引发FullGC长时间停顿。

按照书中作者的经验,在小内存应用上CMS表现大概率优于G1,而大内存上G1会发挥更大的优势,这个临界点在6-8G之间。

  • 空收集器

Epsilon收集器

JDK11发布 是一个空处理器,即不进行垃圾收集。适用于仅需要允许数分钟乃至数秒就会退出的场景(微服务、无服务),无内存管理的运行负载,可最大化提高应用的执行效率。

  • 低延迟收集器

Shenandoah收集器

ZGC收集器

这两个下周再补充吧,下周顺带会附上我的一些实践截图。

本文所有内容来自《深入理解Java虚拟机》一书,属于读书笔记。