JVM 垃圾收集器

本文详细介绍了JVM中的各种垃圾收集器,包括Serial、ParNew、Parallel Scavenge、CMS、G1等,涵盖了基本概念、工作原理及适用场景等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM 垃圾收集器

 

基本概念

 

  • Young Generation:新生代,hotspot vm中又细分为Eden和两个Survivor(from survivor和to survivor)
  • Tenured Generation:老年代
  • Minor GC:只GCyoung generation。
  • Full GC、Major GC:一个意思,指GC整个heap,包括young generation和tenured generation。

 

先了解两个重要的垃圾收集性能的度量:

 

  • Throughput(吞吐量) is the percentage of total time not spent in garbage collection considered over long periods of time. Throughput includes time spent in allocation (but tuning for speed of allocation is generally not needed). throughput = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集的时间) * 100%。
  • Pauses(用户线程停顿时间) are the times when an application appears unresponsive because garbage collection is occurring.因为垃圾收集而导致用户线程暂停的时长。

 

Hotspot包含的所有收集器:

 

 如果两个收集器之间有连线,则这两个收集器可以搭配使用
 

 

一、Serial收集器

 

顾名思义,单线程收集器,“单线程”不仅仅指单个线程或者单个cpu去完成垃圾收集的工作,更重要的是在垃圾收集过程中,所有用户线程都必须暂停,等待整个垃圾收集完成后才恢复执行,官方称为“Stop The World”,新生代young neneration采用停止复制算法,老年代tenured generation采用标记整理算法(如下图)。该收集器是Hotspot在Client模式下的默认新生代young generation的收集器。

 

Serial收集器运行过程图示: 

 

 

Serial Collector有以下特点:

 

  • Serial Collector does Minor Collection and Full Collection serially in a stop-the-world fashion. That is, the Java application execution is halted while collection is taking place.
  • Serial Collector uses the 3-area (Eden, Survivor Space "From" and Survivor Space "To") algorithm for Minor Collections in the Young Generation.改进版的停止复制算法
  • Serial Collector uses the 3-step (Mark, Sweep and Compact) algorithm for Full Collections in the Tenured Generation.标记整理算法
  • Serial Collector is the default garbage collector in HotSpot Client 1.5 and higher.
  • Serial Collector can be specified using the "-XX:+UseSerialGC" command line option.

优点:

  • 相比于其他收集器更简单高效,因为单线程没有线程切换、交互的开销。

缺点:

  • Stop The World对于客户端来说可以忍受,但服务端往往不能

 

二、ParNew收集器

 

Serial收集器的多线程版本,除了使用多个线程执行垃圾收集这一点之外,其余行为和Serial收集器一致。ParNew收集器是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定。默认开启的线程数与CPU的数量相同,如果CPU核数较多,可通过-XX:ParallelGCThreads=<N>来指定线程个数。

ParNew收集器运行过程图示:

 

 

 

三、Parallel Scanvange收集器

 

也称Throughput Garbage Collector,是一个新生代收集器,它是使用停止复制算法的多线程收集器,这些都和ParNew收集器相同,但Parallel Scanvange收集器的目标是达到一个可控制的Throughput(吞吐量),其提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis=<N>(N为毫秒)以及直接设置吞吐量大小的-XX:GCTimeRatio=<N>(0 < N < 100),

 

-XX:MaxGCPauseMillis=<N>:N为大于0的毫秒数,收集器尽可能让垃圾收集过程花费少于N ms,你把该参数设小以后,会导致throughput也变小,因为N变小后,JVM会相应地地新生代的大小调小,从而使单次收集时间变短,但是这也就相应地导致触发GC的次数会增加,从而导致整个JVM周期内用在GC上的时间会边长,吞吐量也就变低了。所以说该收集器是以牺牲吞吐量和新生代空间来换取GC停顿时间的缩短的。

 

-XX:GCTimeRatio=<N>:即User time : GC time,N必须大于0小于100,例如N = 19,则允许的最大GC时间就占总时间的5%(1 / (1 + 19)* 100%)

 

自适应调节策略:Parallel Scanvange收集器还提供一个开关参数-XX:+UseAdaptiveSizePolicy,当打开该开关参数时,JVM会根据垃圾收集的性能信息动态调整新生代的大小、Eden和Survivor的比例(该比例是通过参数-XX:SurvivorRatio设置的)、晋升老年代对象大小(该值是通过参数-XX:PretenuredSizeThreshold指定的)。

 

工作过程如下图:

 

 

 

四、Serial Old收集器

 

 是Serial收集器的老年代版本,也是单线程收集器。

 

 工作过程如下图:

 

 

五、Parallel Old收集器

 

是Parallel Scanvange收集器的老年代版本,使用多线程和标记整理算法。 

 

工作过程如下图:

 

 

 

六、CMS收集器

 

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,采用的是标记清除算法(Mark and Sweep),整个收集过程分四个步骤:

 

  • 初始标记(CMS Initial mark):标记GC Roots能直接关联到的对象。
  • 并发标记(CMS concurrent mark):进行GC Roots Tracing的过程。
  • 重新标记(CMS remark):修正并发标记阶段因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录。
  • 并发清除(CMS concurrent sweep):执行清除过程

耗时的两个阶段并发标记和并发清除都是收集线程和用户线程并发执行的,所以其停顿时间是很短的。

 

缺点:

 

  • CMS收集器无法回收“浮动垃圾”

由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。

 

  • 内存碎片的问题:

CMS收集器是使用“标记-清除”算法收集的,这会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,往往出现老年代还有很大的剩余内存空间但却找不到足够大的一块来容下一个较大的对象,从而来分配不得不提前触发一次Full  GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认该开关参数就是开着的),用于在Full  GC时增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数(默认为0)设置执行多少次不带压缩的Full  GC之后,跟着来一次带压缩的Full GC。

 

 工作过程如下图:



 

 

七、G1收集器(Garbage-First)

 

G1是面向服务端应用的垃圾收集器,有如下特点:

 

    • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记-清理”算法不同,G1从整体看来是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上看是基于“复制”算法实现,无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 可预测的停顿:这是G1相对于CMS的另外一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器特征了。

 在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域

(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

 

  G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验        值),在后台维护一个优先列表,每次根据允许的收集时间,优先回价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限        的时间内获可以获取尽可能高的收集效率。

  G1把内存“化整为零”的思路,理解起来似乎很容易理解,但其中的实现细节却远远没有现象中简单,否则也不会从04年Sun实验室发表第一篇G1的论文拖至今将近8年时间都还没有开发出G1的商用版。笔者举个一          个细节为例:把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?听起来顺理成章,再仔细想想就很容易发现问题所在:Region不可能是孤立的。一个对象分配在某个Region中,它并非只        能被本Region中的其他对象引用,而是可以与整个Java堆任意的对象发生引用关系。那在做可达性判定确定对象是否存活的时候,岂不是还得扫描整个Java堆才能保障准确性?这个问题其实并非在G1中才有,只是        在G1中更加突出了而已。在以前的分代收集中,新生代的规模一般都比老年代要小许多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象也面临过相同的问题,如果回收新生代时也不得不同时扫描老          年代的话,Minor GC的效率可能下降不少。 

  在G1收集器中Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发         现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查引是否老年代中的对象引用了新生代中          的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有         遗漏。

G1收集过程:

1.初始标记:标记GC Roots能直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短

2.并发标记:是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行

3.最终标记:修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的                      数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行

4.筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,从Sun透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分                            Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率

 

工作过程如下图:


The first focus of G1 is to provide a solution for users running applications that require large heaps with limited GC latency. This means heap sizes of around 6GB or larger, and stable and predictable pause time below 0.5 seconds.

推荐使用场景:

1.More than 50% of the Java heap is occupied with live data // 堆中超过50%时存活的对象

2.The rate of object allocation rate or promotion varies significantly // 对象分配频率或年代提升频率变化很大的

3.Undesired long garbage collection or compaction pauses (longer than 0.5 to 1 second) //短停顿的


 

参考:

《深入理解Java虚拟机》

http://www.oracle.com/technetwork/java/javase/tech/g1-intro-jsp-135488.html

http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

### JVM垃圾收集器的工作原理 JVM中的垃圾收集器负责自动管理和释放不再使用的内存资源。这一过程对于维护Java应用程序的性能至关重要[^1]。垃圾收集的主要目标是在不影响应用正常运行的前提下尽可能高效地回收无用对象所占有的空间。 #### 垃圾收集器类型及其特性 多种类型的垃圾收集器存在于现代版本的JVM中,每种都有各自的设计理念来适应特定应用场景下的需求: - **Serial GC**:适用于单核处理器的小规模应用环境,在年轻代采用复制算法,在老年代则使用标记-整理算法。 - **Parallel GC (也称为Throughput Collector)**:专为多CPU系统设计,旨在最大化吞吐量,即完成更多有用工作的比例相对于总执行时间而言。该收集器同样区分新生代与年老代,并分别运用不同的清理策略以达到最佳效果。 - **CMS (Concurrent Mark-Sweep) GC**:专注于降低暂停时间而非整体效率,适合于那些对响应速度敏感的服务端程序。它可以在后台逐步扫描存活对象并清除死亡对象而不必完全停止整个应用程序进程。 - **G1 (Garbage First) GC**:自JDK 7更新版引入以来成为默认选项之一,特别擅长处理具有大量活跃数据的大容量堆配置。G1将整个堆划分为多个固定大小的区域(region),并通过预测哪些地区最有可能包含可回收的空间来进行优先级排序。 - **ZGC 和 Shenandoah GC**:这两种新型低延迟垃圾收集器是从JDK 11开始加入的支持超大型堆(可达数TB级别)的同时具备亚毫秒级别的短暂停滞特性的工具[ZGC][^5]。它们都采用了先进的并发技术使得大部分垃圾回收活动能够在不停止用户线程的情况下发生。 ### 如何选择合适的垃圾收集器? 选择最适合项目需求的垃圾收集器取决于具体的应用场景以及期望达成的目标。如果追求最高的吞吐率,则可能倾向于使用`Parallel GC`; 若更看重快速反应时间和较低的停顿频率,那么像`CMS`, `G1`, 或者最新的`ZGC/Shenandoah`可能是更好的选择。值得注意的是,“最优”的方案并非永恒不变——随着业务逻辑的发展和技术进步,原先选定的最佳实践可能会变得不合适,因此定期审查当前设置总是明智之举[^2]。 ### 性能调优建议 为了使选中的垃圾收集器发挥最大效能,可以通过调整一系列参数来进行精细化控制。这包括但不限于设定初始/最大堆尺寸(-Xms/-Xmx), 新生代占比(-XX:NewRatio), 生存阈值(-XX:+UseAdaptiveSizePolicy,-XX:MaxTenuringThreshold)等。此外,启用详细的日志记录功能可以帮助诊断潜在瓶颈所在之处,从而指导后续改进措施的方向。最终目的是找到一个平衡点,在满足服务等级协议(SLA)关于响应时间和吞吐量的要求之间取得良好折衷。 ```bash java -Xms512m -Xmx4g -XX:+UseG1GC MyApplication ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值