垃圾回收器
JDK 默认垃圾收集器(使用 java -XX:+PrintCommandLineFlags -version
命令查看):
-
JDK 8:Parallel Scavenge(新生代)+ Parallel Old(老年代)
-
JDK 9 ~ JDK20: G1
堆内存中对象的特性:
-
系统中的大部分对象,都是创建出来之后很快就不再使用可以被回收,比如用户获取订单数据,订单数据返回给用户之后就可以释放了。
-
老年代中会存放长期存活的对象,比如Spring的大部分bean对象,在程序启动之后就不会被回收了。
-
在虚拟机的默认设置中,新生代大小要远小于老年代的大小。
分代GC算法将堆分成年轻代和老年代主要原因有:【降低垃圾回收对系统的影响】
1、可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能。
2、新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法【不会有内存碎片,吞吐量比标记算法高】,老年代可以选择标记-清除和标记-整理算法,由程序员来选择灵活度较高。
3、分代的设计中允许只回收新生代(minor gc),如果能满足对象分配的要求就不需要对整个堆进行回收(full gc),STW时间就会减少。【吞吐量增大。因为FullGC耗时长】避免FullGC
-
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
-
而老年代的对象存活几率是比较高的(需要回收的少),而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
垃圾回收器是垃圾回收算法的具体实现。
由于垃圾回收器分为年轻代和老年代,除了G1之外其他垃圾回收器必须成对组合进行使用。
具体的关系图如下:
年轻代-Serial(串行)垃圾回收器
-
新生代采用标记-复制算法,老年代采用标记-整理算法。
复制算法
它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
老年代-SerialOld垃圾回收器
标记-整理算法
CMS 收集器的后备方案
Serial 收集器的老年代版本
年轻代-ParNew垃圾回收器
-
新生代采用标记-复制算法,老年代采用标记-整理算法。
复制算法
Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
运行在 Server 模式下的虚拟机的首要选择
并行和并发概念补充:
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。【CMS真正意义上的并发收集器】
老年代- CMS(Concurrent Mark Sweep)垃圾回收器
【有设计上的缺陷,所以JDK14废除了】
标记-清除算法【一般不会老年代单独标记清除,是在标记清除的基础上在FullGC时整理】
STW:初始标记、重新标记
以获取最短回收停顿时间为目标的收集器
第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。【并发】
初始标记和重新标记不是会暂停用户线程
CMS执行步骤:
1.初始标记,用极短的时间标记出GC Roots能直接关联到的对象。
2.并发标记, 标记所有的对象,用户线程不需要暂停。
3.重新标记,由于并发标记阶段有些对象会发生了变化,存在错标、漏标等情况,需要重新标记。【STW时间:初始标记 < 重新标记 << 并发标记】
4.并发清理,清理死亡的对象,用户线程不需要暂停。
缺点:【标记清除,但有FullGC】
1、CMS使用了标记-清除算法,在垃圾收集结束之后会出现大量的内存碎片,CMS会在Full GC时进行碎片的整理。这样会导致用户线程暂停,可以使用-XX:CMSFullGCsBeforeCompaction=N 参数(默认0)调整N次Full GC之后再整理。【弥补措施,但还是跑不掉】
2.、无法处理在并发清理过程中产生的“浮动垃圾”,不能做到完全的垃圾回收。【并发清理过程中产生新垃圾,没有标记】
3、如果老年代内存不足无法分配对象,CMS就会退化成Serial Old单线程回收老年代。【降级策略】
年轻代-Parallel(并行) Scavenge垃圾回收器
-
新生代采用标记-复制算法,老年代采用标记-整理算法。
关注吞吐量,自动调整堆内存
复制算法、自动调整堆【提供了很多参数供用户找到最合适的停顿时间或最大吞吐量】
【垃圾回收会产生STW】
常用参数:
Parallel Scavenge允许手动设置最大暂停时间和吞吐量。Oracle官方建议在使用这个组合时,不要设置堆内存的最大值,垃圾回收器会根据最大暂停时间和吞吐量自动调整内存大小。
最大暂停时间,
-XX:MaxGCPauseMillis=n
设置每次垃圾回收时的最大停顿毫秒数吞吐量,
-XX:GCTimeRatio=n
设置吞吐量为n(用户线程执行时间 = n/n + 1)自动调整内存大小,
-XX:+UseAdaptiveSizePolicy
设置可以让垃圾回收器根据吞吐量和最大停顿的毫秒数自动调整内存大小
老年代-Parallel Old垃圾回收器
标记-整理算法【一般不会老年代单独标记清除,是在标记清除的基础上在FullGC时整理】
-
注重吞吐量以及 CPU 资源
G1垃圾回收器
面向服务器的垃圾收集器,主要针对配备多核处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
特征:
并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。
JDK9之后默认的垃圾回收器是G1(Garbage垃圾 First)垃圾回收器。
-
Parallel Scavenge关注吞吐量,允许用户设置最大暂停时间 ,但是会减少年轻代可用空间的大小。
-
CMS关注暂停时间【追求变短】,但是吞吐量方面会下降。
而G1设计目标就是将上述两种垃圾回收器的优点融合:【在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region】
1.支持巨大的堆空间回收,并有较高的吞吐量。
2.支持多CPU并行垃圾回收。
3.允许用户设置最大暂停时间。
JDK9之后强烈建议使用G1垃圾回收器。
-
G1出现之前的垃圾回收器,年轻代和老年代一般是连续的,如下图:
-
G1的整个堆会被划分成多个大小相等的区域,称之为区Region,区域不要求是连续的。分为Eden、Survivor、Old区。Region的大小通过堆空间大小/2048计算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(其中32m指定region大小为32M),Region size必须是2的指数幂,取值范围从1M到32M。
G1垃圾回收有两种方式:
1、年轻代回收(Young GC)
2、混合回收(Mixed GC)
年轻代回收
年轻代回收(Young GC),回收Eden区和Survivor区中不用的对象。会导致STW,G1中可以通过参数-XX:MaxGCPauseMillis=n
(默认200) 设置每次垃圾回收时的最大暂停时间毫秒数,G1垃圾回收器会尽可能地保证暂停时间。
1、新创建的对象会存放在Eden区。当G1判断年轻代区不足(max默认60%),无法分配对象时需要回收时会执行Young GC。
2、标记出Eden和Survivor区域中的存活对象,
3、根据配置的最大暂停时间选择某些区域将存活对象复制【复制,没有内存碎片】到一个新的Survivor区中(年龄+1),清空这些区域。
-
G1在进行Young GC的过程中会去记录每次垃圾回收时每个Eden区和Survivor区的平均耗时,以作为下次回收时的参考依据。这样就可以根据配置的最大暂停时间计算出本次回收时最多能回收多少个Region区域了。
比如 -XX:MaxGCPauseMillis=n(默认200),每个Region回收耗时40ms,那么这次回收最多只能回收4个Region。
4、后续Young GC时与之前相同,只不过Survivor区中存活对象会被搬运到另一个Survivor区。
5、当某个存活对象的年龄到达阈值(默认15),将被放入老年代。
6、部分对象如果大小超过Region的一半,会直接放入老年代,这类老年代被称为Humongous区。比如堆内存是4G,每个Region是2M,只要一个大对象超过了1M就被放入Humongous区,【如果对象过大会横跨多个Region】。、
7、多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时。(-XX:InitiatingHeapOccupancyPercent默认45%)会触发混合回收MixedGC。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成。
混合回收
混合回收分为:初始标记(initial mark)、并发标记(concurrent mark)、最终标记(remark或者Finalize Marking)、并发清理(cleanup)【重新标记和CMS不一样】
G1对老年代的清理会选择存活度最低的区域来进行回收,这样可以保证回收效率最高,这也是G1(Garbage first)名称的由来。最后清理阶段使用复制算法,不会产生内存碎片。【不会全部清理,效率变快】
-
漏标和CMS不一样,只补充由于他的引用改变了导致漏标的。【旧的】所以比CMS快
-
注意:如果清理过程中发现没有足够的空Region存放转移的对象,会出现Full GC。单线程执行标记-整理算法,此时会导致用户线程的暂停。所以尽量保证应该用的堆内存有一定多余的空间。
总结
垃圾回收器的组合关系虽然很多,但是针对几个特定的版本,比较好的组合选择如下:
JDK8及之前:
ParNew + CMS(关注暂停时间)、Parallel Scavenge + Parallel Old (关注吞吐量)、 G1(JDK8之前不建议,较大堆并且关注暂停时间)
JDK9之后:
G1(默认)
从JDK9之后,由于G1日趋成熟,JDK默认的垃圾回收器已经修改为G1,所以强烈建议在生产环境上使用G1。
(此处)并行和并发概念补充
-
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
-
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。【CMS真正意义上的并发收集器】