原文地址:https://xeblog.cn/articles/24
image新生代收集器
新生代均采用
复制
算法来回收内存。
Serial 收集器
最基本的、发展历史最悠久的单线程的收集器。在进行垃圾收集时,必须暂停其它所有的工作线程(Stop The World),直到它收集结束。它是 JVM
运行在 Client
模式下的默认新生代收集器,它比其它单线程的收集器更简单、更高效。可与 CMS 收集器
配合工作。
ParNew 收集器
Serial 收集器
的多线程版本。它是许多运行在 Server
模式下的虚拟机首选的新生代收集器,在使用 -XX:+UseConcMarkSweepGC
选项后的默认新生代收集器,也可以通过 -XX:+UseParNewGC
选项强制指定它。它除了是多线程收集之外,其它和 Serial 收集器
基本一样,它默认开启的收集线程数与 CPU
的数量相同,在 CPU
非常多的环境下,可通过 -XX:ParallelGCThreads
参数限制垃圾收集的线程数。可与 CMS 收集器
配合工作。
Parallel Scavenge 收集器
并行的多线程收集器,它的目标是达到一个可控的吞吐量(吞吐量是 CPU
用于运行用户代码的时间与 CPU
总消耗时间的比值)。
吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集时间)
高吞吐量可以高效的利用 CPU
时间,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。Parallel Scavenge 收集器
提供了两个参数用于精确控制吞吐量: -XX:MaxGCPauseMillis
和 -XX:GCTimeRatio
。
最大垃圾收集停顿时间:-XX:MaxGCPauseMillis
允许一个大于0的毫秒数,收集器将尽可能保证内存回收花费时间不超过这个设定的值,它是以牺牲吞吐量和新生代空间来缩短 GC
停顿时间的。
吞吐量大小:-XX:GCTimeRatio
允许一个大于0且小于100的整数,垃圾收集时间占总时间的比率。
GC自适应调节参数:-XX:+UseAdaptiveSizePolicy
这是一个开关参数,当它打开后,就不需要手动指定新生代的大小(-Xmn),Eden
与 Survivor
区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量。
老年代收集器
老年代采用
标记-整理
或标记-清除
算法来回收内存。
Serial Old 收集器
它是 Serial 收集器
的老年代版本,也是一个单线程收集器,使用 标记-整理
算法。这个收集器的主要意义也是在于给 Client
模式下的虚拟机使用,如果是在 Server
模式下,那么它主要还有两大用途:
- 在
JDK1.5
以及之前版本中与Parallel Scavenge 收集器
搭配使用。 - 作为
CMS收集器
的后备预案,在并发收集发生Concurrent Mode Failure
时使用。
Parallel Old 收集器
它是 Parallel Scavenge 收集器
的老年代版本,使用多线程和 标记-整理
算法。
CMS 收集器 (Concurrent Mark Sweep)
它是一种并发的、以获取最短回收停顿时间为目标的收集器,使用 标记-清除
算法实现的。运行过程较为复杂,整个过程分为4个步骤:
- 初始标记(CMS Initial Marking)
- 并发标记(CMS Concurrent marking)
- 重新标记(CMS ReMarking)
- 并发清除(CMS Concurrent Sweep)
初始标记、重新标记仍需要暂停其它所有的工作线程,初始标记仅仅只是标记一下 GC Roots
能直接关联到的对象,速度很快。并发标记阶段就是进行 GC Roots
的可达性分析过程,而重新标记阶段则是为了修正并发标记期间因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。CMS 收集器
存在3个明显的缺点:
-
CMS收集器
会占用一部分线程而导致应用程序变慢,总吞吐量会降低。CMS
默认启功的回收线程数是(CPU数量 + 3)/ 4
,相当于当CPU
在4个以上时,并发回收时垃圾收集线程不少于25%
的CPU
资源,并随着CPU
数量的增加而下降。当CPU
不足4个时,CMS
对用户线程的影响就可能变得更大。 -
CMS收集器
无法处理浮动垃圾,可能出现Concurrent Mode Failure
失败而导致另一次Full GC
的产生。由于CMS
并发清除阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS
无法在当次收集中处理掉它们,只好留在下一次GC
时再清理掉,这一部分垃圾就称为浮动垃圾。 - 由于
CMS收集器
使用的是标记-清除
算法,所以垃圾回收后会产生大量的空间碎片。CMS
提供-XX:+UseCMSCompactAtFullCollection
的开关参数(默认开启),用于在CMS
收集器顶不住要进行Full GC
时开启内存碎片的合并整理过程,这个过程是无法并发执行的,虽然空间碎片没了,但是停顿时间不得不变长。
全干工程师:G1收集器
G1收集器
是当今收集器技术发展的最前沿成果之一。
上面所介绍的收集器,都只是负责 Java堆
中的一部分内存(新生代或老年代),而 G1
就不同了,它全干。
G1
将整个 Java堆
划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但是这两部分内存不再是物理隔离的,它们都是一部分不需要连续的 Region
的集合了。
具备的特点:
- 并行与并发:
G1
能充分利用多CPU
、多核环境下的硬件优势,使用多个CPU
来缩短Stop The World
的停顿时间。 - 分代收集(全干工程师):
G1
可以不需要与其它收集器配合就能独立管理整个GC堆
,根据分代收集
的概念采用不同的方式去处理。 - 空间整合:基于
标记-整理
和复制
算法,不会产生内存空间碎片。 - 可预测的停顿:
G1
可以建立可预测的停顿时间模型,将垃圾收集消耗的时间限制在一定的时间内。
可预测的停顿时间模型
G1
之所以能够建立可预测的停顿时间模型,是因为它可以有计划的避免在整个 Java堆
中进行全区域的垃圾收集。G1
通过跟踪各个 Region
里面的内存堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先回收队列,每次根据允许的收集时间,优先回收价值最大的 Region
。这种使用 Region
划分内存空间以及有优先级的区域回收方式,保证了 G1
收集器在有限的时间内可以获取尽可能高的收集效率。
避免全堆扫描且保证准确性
在 G1
收集器中,Region
之间的对象引用以及其它收集器中的新生代与老年代之间的对象引用,虚拟机都是使用 Remembered Set
来避免全堆扫描的。G1
中的每一个 Region
都有一个与之对应的 Remembered Set
,在对 Reference
类型的数据进行写操作的时间,虚拟机会产生一个屏障暂时中断写操作,然后检查这个引用的对象是否处于不同的 Region
之中,如果是,就会通过 CardTable
把相关引用信息记录到被引用对象所属的 Region
的 Remembered Set
中。当进行内存回收时,在 GC Roots
的枚举范围内加入 Remembered Set
即可保证不对全堆扫描也不会有遗漏。
G1运行过程
如果不计算维护 Remembered Set
的操作,G1
收集器的运行过程大致可以分为4个步骤:
- 初始标记(G1 Initial Marking)
- 并发标记(G1 Concurrent Marking)
- 最终标记(G1 Final Marking)
- 筛选回收(G1 Live Data Counting And Evacuation)
初始标记阶段只是简单的标记一下 GC Roots
能直接关联到的对象,并且修改TAMS(Next Top At Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region
中创建新对象,这个阶段需要停顿线程,但耗时很短。并发标记阶段也是进行 GC Roots
的可达性分析过程,耗时很长但是可与用户程序一起工作。最终标记阶段是为了修正并发标记期间因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs
中,这个阶段需要把 Remembered Set Logs
中的数据合并到 Remembered Set
里,需要停顿线程,但是可以并行执行。
最后的筛选回收阶段会对 Region
的回收价值和成本进行排序,根据用户所期望的 GC
停顿时间来制定回收计划。
查看JVM所使用的收集器
java -XX:+PrintCommandLineFlags -version
在终端执行上述命令后,即可查看 JVM
所使用的收集器。
-XX:+UseParallelGC : 是虚拟机运行在 Server
模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old (PS MarkSweep)
的收集器组合进行内存回收。
参考
- 《深入理解Java虚拟机:JVM高级特性与最佳实践 第二版》
网友评论