如果你的世界,没有痛苦的害怕,没有尊严的担忧,没有富贵的贫贱,没有暖寒的交替,没有外貌的困扰,没有男女的区别,没有你我之分,没有生死顾虑,你才会离"真正的活着"越来越近。——余华 《活着》
在上一篇内容中我们提到在Java虚拟机中主要是通过标记整理、复制拷贝、标记清除
三种算法进行内存回收的,在本篇内容中我们将继续了解Java虚拟机中有哪些收集器,分别采用哪种算法进行实现的,通过垃圾收集器的应用了解什么是分代收集器,分代收集器与分区收集器又有什么区别,在Java9之后默认的G1收集器的优点是什么?
内容要点概览:
-
Java虚拟机中采用分代收集算法,通过标记整理、复制拷贝、标记清除等方式进行内存回收。
-
分代收集算法的优点是可以根据不同年代内存区块采用最适合的GC算法,提高垃圾回收效率。
-
区分分代收集算法和分区收集算法,后者通过将堆空间精细地划分为一系列小区域,实现更为精细化的内存管理。
-
HotSpot虚拟机中提供了多种垃圾收集器,包括Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old、G1、ZGC、Shenandoah、Epsilon等。
-
根据应用的具体需求和运行环境选择最合适的垃圾收集器,比如吞吐量优化、并发级别、暂停时间目标等。
1.分代收集算法
在《一文带你了解Java虚拟机垃圾回收算法》中我介绍了标记整理、复制拷贝、标记清除
三种算法,但在当前主流的Java虚拟机中一般都是采用分代收集的算法,这种算法会在内存中开辟不同的逻辑空间,划分为不同内存块,如在内存分配的写到的新生代、老年代、永久代,通过这种方式就可以根据不同年代内存区块采用最适当的GC算法(垃圾回收算法),比如在JVM中通常在新生代会采用复制算法,而在老年代中则采用标记整理算法。而采用这种方式的优点有以下:
-
新生代-复制算法:每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集.
-
老年代-标记整理算法:因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.
2.分区收集算法
与分代收集算法不同的是,在分区收集算法中不存在分代的说法,它通过将堆空间精细地划分为一系列连续且独立的小区域,即小区间,实现了更为精细化的内存管理。每个这样的小区间被独立地分配和回收,这一策略的核心优势在于灵活性和可控性:
系统可以根据实际的性能需求,特别是目标响应时间或停顿时间,动态决定每次垃圾回收操作所涉及的小区间数量。这意味着,相较于传统的全堆回收,分区算法能够更加精准地平衡回收效率与应用停顿,确保在满足性能指标的同时,高效地回收不再使用的内存资源,显著降低了单次GC(Garbage Collection,垃圾回收)过程对应用程序造成的中断影响。
这种策略特别适用于对延迟敏感的应用场景,通过避免一次性回收整个堆所带来的长时间停顿,保证了系统的流畅运行和用户体验。
3.GC 垃圾收集器
在上述中我们了解分代收集器和分区收集器的区别,那么在HotSpot虚拟机中提供哪些垃圾收集器呢?我们通过下图来做一个直观上的了解:
通过上图可以看到,HotSpot 虚拟机中提供了不同的垃圾收集器,其中Serial
,ParNew
,Parallel Scavenge
属于年轻代中使用的垃圾收集器,CMS
,Serial Old
,Parallel Old
属于老年代中使用的垃圾收集器,中间的连线代表着可以搭配使用的收集器。另外图中的G1
,ZGC
,Shenandoah
,Epsilon
这四种属于所有代均可使用的垃圾收集器。
在Java 虚拟机中提供了这么多的垃圾收集器,那么它们的特性分别是什么呢,当默认使用的垃圾收集器无法更好的满足我们的需求时要如何去选择?
3.1 Serial 垃圾收集器
Serial垃圾收集器,是JVM中最基础的垃圾回收机制,采用了简单的复制算法。在JDK 1.3.1及以前的版本中,它是新生代垃圾收集的唯一选择。尽管Serial收集器仅利用单个CPU或单一工作线程执行垃圾回收任务,且在此期间会暂停所有应用程序线程直至回收过程完成,但这种设计却赋予了它在限定的单CPU环境中无与伦比的效率。由于不存在多线程间的交互成本,Serial收集器能实现最高水平的单线程垃圾回收性能。
正因如此简洁高效的特性,即使在现代多核处理器环境下,Serial收集器仍然是Java虚拟机运行于Client模式下的默认新生代垃圾收集器选项。在客户端应用中,由于通常追求较快的启动速度和较小的内存占用,而非最大化应用的吞吐量,因此Serial收集器凭借其低资源消耗和快速的初始化过程,成为了这一场景下的理想选择。
简而言之,Serial收集器虽牺牲了并行处理能力,但在单线程环境中,却能以最小的系统开销提供最优化的垃圾回收效果,尤其适用于那些对响应时间和系统资源有严格限制的客户端应用环境。
3.2 ParNew 垃圾收集器
ParNew垃圾收集器就像是Serial收集器的升级版,它把单线程变成了多线程,就像是从独木桥升级到了高速公路。它还是用的复制算法,只不过现在有多个工人同时干活,效率自然高了不少。不过,干活的时候,它还是会叫停所有的小伙伴,也就是其他的工作线程,直到垃圾清理完毕。
ParNew这哥们儿,默认情况下会根据你的CPU核心数量来安排线程,也就是说,核心越多,干活的线程也就越多。当然,如果你觉得太多了,也可以用-XX:ParallelGCThreads
参数来给它设个上限,让它别那么拼命。
尽管ParNew和Serial长得差不多,只是多了个多线程的功能,但它可是Server模式下Java虚拟机新生代的宠儿,几乎是默认的垃圾收集器首选。毕竟,Server模式下,我们得考虑的是稳定性和高负载,ParNew正好能满足这些需求。
3.3 Parallel Scavenge 收集器
Parallel Scavenge收集器,这家伙也是新生代里的清洁工,用的复制算法,而且是个多线程的狠角色。不过,它的心思不在多快干完活,而是在于怎么让程序跑得又快又稳,就是所谓的“吞吐量”。说得直白点,就是CPU花在跑你程序代码上的时间占总时间的比例。高吞吐量就意味着CPU大部分时间都在忙正事,快速搞定任务,特别适合那些后台默默干活,不需要跟人互动太多的程序。
Parallel Scavenge有个绝活,就是自适应调节策略,这是它跟ParNew最大的不同。它能自动调整各种参数,比如垃圾回收的频率和强度,确保吞吐量保持在你设定的目标上。这样一来,你就不用操心太多细节,专注于你的应用逻辑就行了。总之,如果你的应用是个工作狂,需要长时间、高强度运行,那Parallel Scavenge绝对是你的最佳拍档。
3.4 Serial Old 收集器
Serial Old收集器,作为Serial收集器的年老代版本,延续了其单线程操作的特点,采用标记-整理算法进行垃圾回收。此收集器在Java虚拟机运行于Client模式下,被视为默认配置中针对年老代区域的垃圾回收机制。 在Server模式下,Serial Old收集器则展现出更为特定的应用场景:
-
在JDK1.5及更早版本中,它与新生代的Parallel Scavenge收集器协同工作,形成一套高效的垃圾回收组合,共同维护整个堆内存的健康状态。
-
作为年老代中CMS(Concurrent Mark Sweep)收集器的辅助计划,Serial Old在CMS遇到性能瓶颈或清理效果不佳的情况下,能够无缝切换至自身,确保年老代区域的垃圾回收得以有效执行,从而维持系统的稳定运行。
综上所述,无论是在早期版本的JDK中与Parallel Scavenge收集器的互补,还是作为CMS收集器的备份方案,Serial Old收集器均扮演着不可或缺的角色,确保了Java虚拟机在不同运行模式下,能够高效、稳健地管理内存资源。
3.5 Parallel Old 收集器
在JDK1.6问世之前,尽管Parallel Scavenge收集器已在新生代中崭露头角,致力于提升吞吐量表现,然而,与其搭档的年老代收集器却局限于Serial Old,仅能确保新生代区域的高效运行,而无法同步优化整个系统的吞吐量表现。Parallel Old收集器的诞生,正是为了弥补这一缺憾,它作为Parallel Scavenge的年老代对应版本,引入了多线程的标记-整理算法,旨在年老代同样实现吞吐量优先的垃圾回收策略。
对于那些对吞吐量有着严苛要求的应用场景,Parallel Old与Parallel Scavenge的联袂登场,无疑提供了最佳的解决方案。通过在新生代与年老代均采用吞吐量导向的收集器,不仅能够显著提高程序运行效率,还能确保整个系统的资源利用更加合理,进而满足高性能计算与大规模数据处理的需求。因此,当系统对吞吐量有较高期待时,Parallel Scavenge与Parallel Old的组合策略,无疑是优先考量的优选方案。
3.6 CMS 收集器
CMS(Concurrent Mark Sweep)收集器是针对老年代的优化策略,旨在实现最短的垃圾回收停顿时间,以提升高交互性应用的用户体验。不同于其他采用标记-整理算法的老年代收集器,CMS采用多线程的标记-清除方法,其复杂的工作流程包括四个关键步骤:
-
初始标记:快速定位GC Roots直接关联的对象,虽需短暂暂停所有工作线程,但速度快。
-
并发标记:与用户线程并行运行,追踪GC Roots,期间不影响应用程序的正常执行。
-
重新标记:修正并发标记期间因程序运行导致的对象标记变化,这一步仍需暂停工作线程以保证准确性。
-
并发清除:清理GC Roots不可达的对象,这一阶段同样与用户线程并发执行,避免长时间阻塞。 整体来看,CMS通过在耗时较长的并发标记和并发清除阶段与用户线程协作,实现了与应用程序的大部分时间并发执行,从而显著降低了系统停顿时间。
3.7 G1 收集器
G1(Garbage first)收集器是从JDK9版本之后默认提供的垃圾收集器,采用的算法是标记-整理算法,是目前Java虚拟机中比较前沿的垃圾收集器。
相较于上述的收集器,在G1收集器中取消分代收集的概念。
G1垃圾收集器创新性地将堆内存细分为多个固定大小的独立区域,同时精密监控各区域的垃圾收集状态,构建了一个动态更新的优先级列表。这一机制使得G1能够在后台智能评估,依据当前可分配的收集时间,优先对垃圾含量最高的区域进行清理。通过区域划分与优先级回收的双重保障,G1收集器实现了在预设时间内最大化垃圾回收效率的目标,确保了系统在有限资源下仍能保持高性能与稳定性。这种设计不仅提升了垃圾回收的针对性,还有效减少了全局停顿时间,为高并发、大数据量的应用场景提供了强有力的支持。
3.8 ZGC,Shenandoah 和 Epsilon 垃圾收集器
这三个收集器采用的都是分区收集算法,但在目前的实践中运用的比较少,这边简单做了解即可。
ZGC 最初在 JDK 11 中作为实验性特性加入,随后在后续版本中得到进一步的完善和增强。它是一个针对大规模内存应用设计的低延迟垃圾收集器,能够维持非常短的暂停时间,即使在处理 GB 级别的堆大小时也能保持良好的性能
Shenandoah 在 JDK 12 中作为实验性特性引入,它同样专注于低暂停时间的垃圾收集,适用于对响应时间有严格要求的应用场景。与 ZGC 类似,Shenandoah 也在后续版本中持续得到改进和优化。
Epsilon Collector 是在 JDK 11 中被引入,这是一个特殊的垃圾收集器,实际上并不执行垃圾收集工作,而是允许 JVM 运行直到内存耗尽,然后终止。Epsilon 主要用于测试和基准测量,当不需要垃圾收集的开销时,可以作为一种选择。
在HotSpot虚拟机中给我们提供丰富的垃圾收集算法,每种垃圾收集器都有其独特的特性和参数,例如并发级别、暂停时间目标、吞吐量优化等,我们可以根据应用的具体需求和运行环境选择最合适的垃圾收集器。
4.总结
在本篇内容中我们探讨了Java虚拟机中的垃圾收集器,首先介绍了分代收集算法的基本概念和优劣,并区分了分代收集与分区收集算法的主要区别。接着详细分析了几种常见的垃圾收集器,包括Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS以及G1等,它们各自采用不同的算法(如复制、标记-整理、标记-清除等)进行垃圾回收,并针对不同的应用场景提供了优化。另外还说明了G1收集器在Java9之后成为默认收集器的原因,以及它如何通过区域划分与优先级回收来提高垃圾回收效率和减少停顿时间。此外,也提及了一些较为实验性的垃圾收集器如ZGC、Shenandoah和Epsilon,它们各自致力于解决特定问题,如大规模内存应用的低延迟处理等。
总的来说,Java虚拟机提供了多种垃圾收集器以适应不同的需求,开发者可根据具体的应用场景选择最适合的收集器和算法,以达到最优的性能和资源管理。
标签:Java,收集器,虚拟机,回收,算法,垃圾,Serial,Parallel From: https://blog.csdn.net/qq_37677132/article/details/140245027