首页 > Java > Java面试题 > 正文

说一下jvm 有哪些垃圾回收算法?

幻夢星雲
发布: 2025-10-31 01:01:24
原创
741人浏览过
JVM垃圾回收算法主要有标记-清除、复制和标记-整理三种,分别适用于不同内存区域。标记-清除易产生碎片,复制算法以空间换时间,适合新生代,标记-整理则解决碎片问题,适合老年代。JVM结合多种算法,基于对象生命周期差异实现分代回收,提升性能。现代GC器如G1、ZGC、Shenandoah通过区域化管理、并发处理和读屏障等技术,在大堆场景下实现低延迟与高吞吐的平衡。选择合适的GC器需根据应用类型、堆大小、对象分配速率和硬件资源综合考量,并通过日志分析与调优持续优化。

说一下jvm 有哪些垃圾回收算法?

JVM的垃圾回收算法主要有三种基本类型:标记-清除(Mark-Sweep)、复制(Copying)和标记-整理(Mark-Compact)。它们各自有其优缺点,并且在实际的JVM实现中,往往会结合使用,以适应不同的内存区域和回收需求。

解决方案

要说JVM的垃圾回收,首先得明白它核心的那几个套路。在我看来,所有复杂的GC器,骨子里都离不开这三种基本算法的影子,只是它们被包装得更精巧,或者说,有了更高级的优化策略。

标记-清除(Mark-Sweep)算法 这是最基础的一种,理解起来也直观。它分两步走:

  1. 标记(Mark):从根对象(GC Roots)开始,遍历所有可达的对象,把它们标记出来。这些被标记的对象就是“活”的,不能被回收。
  2. 清除(Sweep):遍历整个堆,把所有没有被标记的对象(也就是不可达的“死”对象)清除掉。 这种算法有个明显的优点,它不需要移动对象,所以效率相对高,而且不会占用额外的空间。但缺点也挺突出:它会产生大量的内存碎片。想想看,一块大内存被回收后,可能会留下很多小块的“坑”,如果后续需要分配一个大对象,即使总内存是够的,也可能因为没有连续的足够空间而触发另一次GC,甚至导致OOM。这就像你的硬盘,删了很多文件,但碎片化严重,找个大文件都费劲。

复制(Copying)算法 这个算法的思路就完全不同了,它更像是一种“以空间换时间”的策略,特别适合那些生命周期短的对象。它将可用内存分成大小相等的两块,每次只使用其中一块。当这块内存用完了,就将还“活着”的对象复制到另一块空闲的内存上,然后把当前使用的这块内存全部清理掉。

  1. 复制(Copy):将当前已用空间中的存活对象,全部复制到另一块未使用的空间。
  2. 清空(Empty):将当前已用空间直接清空。 它的优点是显而易见的:不会产生内存碎片,而且复制过程中,只要移动堆顶指针就能分配内存,效率很高。但它的缺点也很明显:内存利用率只有50%,因为你总要留一块备用空间。这在寸土寸金的生产环境里,有时候是不能接受的。所以,它通常只用于新生代(Young Generation),因为新生代的对象大部分都是朝生夕死的,存活率很低,复制的成本相对可控。

标记-整理(Mark-Compact)算法 标记-整理是标记-清除的升级版,它在标记之后,并没有直接清除,而是多了一步“整理”。

  1. 标记(Mark):同样,从GC Roots开始,标记出所有存活的对象。
  2. 整理(Compact):将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 这种算法解决了标记-清除的内存碎片问题,而且内存利用率高,因为它不需要像复制算法那样预留空间。但它的缺点也很明显:对象移动的成本很高,尤其是在老年代这种对象数量多、存活率高的区域,每次GC都移动大量对象,会造成较长时间的STW(Stop-The-World),也就是应用暂停。这对于那些对响应时间要求高的应用来说,是致命的。

为什么JVM垃圾回收需要多种算法?它们如何协同优化性能?

你可能会问,既然有这么多算法,为什么不选一个最好的用到底?我的理解是,没有“最好”的算法,只有“最合适”的算法。JVM之所以需要多种垃圾回收算法,并让它们协同工作,核心原因在于对象的生命周期差异和性能目标的多样性。

设想一下,你有一个繁忙的办公室,有些文件(对象)刚用完就扔了,有些文件要长期保存。如果用同一种方式处理,效率肯定不高。JVM正是基于这种“分代假设”(Generational Hypothesis)来设计的:绝大多数对象都是朝生夕死的,而少数对象会长期存活。

因此,JVM把堆内存分成了不同的区域,最常见的就是新生代(Young Generation)和老年代(Old Generation)。

  • 新生代:这里的对象生命周期短,GC频繁。为了追求高效率和避免碎片,通常会使用复制算法。因为存活对象少,复制成本低,而且能够快速回收大量空间。新生代通常还会细分为一个Eden区和两个Survivor区(From和To),这就是复制算法的具体实现。对象先在Eden区分配,GC时,存活对象被复制到其中一个Survivor区,下次GC再复制到另一个,经过几次GC仍存活的对象才会被晋升到老年代。
  • 老年代:这里的对象生命周期长,GC不那么频繁,但每次GC涉及的对象数量多。如果用复制算法,成本太高,而且老年代的内存利用率要求更高。所以,老年代通常会选择标记-整理算法来解决碎片问题,或者使用标记-清除算法(如果对碎片不那么敏感,或者后续有整理的机制)来降低STW时间。当然,现代的GC器,比如CMS、G1,对老年代的回收做了大量优化,试图在吞吐量和延迟之间找到更好的平衡。

这种分代协同的策略,本质上是一种优化:用最适合的算法处理最合适的区域。新生代用复制,追求高吞吐量和低延迟;老年代用标记-整理或更复杂的算法,解决碎片和长期存活对象的回收问题。它们就像是流水线上的不同工位,各司其职,共同完成了垃圾回收这个大任务,以最小的代价维持了应用的持续运行。

现代JVM垃圾回收器:从CMS到ZGC,技术演进与核心特性解析

如果说前面讲的是GC算法的“基本功”,那么现代JVM的垃圾回收器就是这些基本功的集大成者,并且加入了大量创新,旨在解决传统GC器面临的痛点。我们追求的无非是两点:高吞吐量(单位时间内处理更多任务)和低延迟(响应时间快)。但这两者往往是鱼和熊掌,难以兼得。

  • CMS(Concurrent Mark Sweep)收集器: CMS是HotSpot JVM中第一个真正意义上的并发收集器,它的目标是降低GC时的停顿时间(latency),特别适用于对响应时间敏感的Web应用。它主要用于老年代。 它的核心思想是:在标记和清除阶段,尽可能地让GC线程和应用线程并发执行,减少STW时间。它有几个关键步骤:初始标记(STW,但很快)、并发标记(与应用并发)、重新标记(STW,修正并发标记期间对象的变化)、并发清除(与应用并发)。 听起来很美,但CMS也有它的问题:

    1. 浮动垃圾(Floating Garbage):在并发清除阶段,应用还在运行,可能会产生新的垃圾,这部分垃圾只能等到下次GC才能清理,这就是浮动垃圾。
    2. 内存碎片:CMS是基于标记-清除算法的,所以它会产生内存碎片。当碎片过多时,如果需要分配大对象,可能无法找到连续空间,导致提前触发Full GC,而Full GC是STW的。
    3. 对CPU资源敏感:并发执行意味着需要更多的CPU核心来支持GC线程。
  • G1(Garbage-First)收集器: G1是Oracle在JDK 7中推出的,旨在取代CMS,成为下一代低延迟、高吞吐量的收集器。它的设计理念非常独特:它将整个Java堆划分为多个大小相等的独立区域(Region)。每个Region都可以独立地作为Eden、Survivor或者Old区。 G1的核心优势在于:

    1. 可预测的停顿时间:G1允许用户指定一个GC停顿的目标时间(例如,不超过200毫秒)。G1会根据这个目标,选择回收价值最高(垃圾最多)的Region进行回收,这就是“Garbage-First”的由来。
    2. 避免内存碎片:G1在回收Region时,采用的是复制和标记-整理算法的混合模式。它会把存活对象从一个或多个Region复制到新的Region中,这本身就带有整理的效果,因此碎片问题得到了很好的缓解。
    3. 分代和非分代并存:G1依然保留了分代的概念,但它的区域划分让它在处理大对象(Humongous Object)时更灵活,可以直接在老年代区域分配。
  • ZGC和Shenandoah收集器: 这是JDK 11之后出现的,代表了JVM GC的最新发展方向,它们的目标是将GC停顿时间控制在极低的水平(通常是10毫秒以内,甚至更低),即便是在TB级别的堆内存下也能保持。它们的核心技术突破在于使用了着色指针(Colored Pointers)读屏障(Read Barriers)。 简单来说,它们通过在指针中编码GC状态信息,并在每次对象访问时插入读屏障来检测并处理并发GC操作,从而实现了几乎完全并发的垃圾回收。这意味着GC的绝大部分工作可以与应用线程并发执行,STW时间极短,几乎可以忽略不计。

    • ZGC:由Oracle开发,目标是极低的延迟。它利用了多重映射(Multi-Mapping)技术,将虚拟内存区域映射到不同的物理内存区域,实现并发移动对象。
    • Shenandoah:由Red Hat开发,也追求极低延迟。它使用转发指针(Forwarding Pointers)来实现并发移动对象。 这些收集器对于超大规模堆内存、对延迟极其敏感的应用(如金融交易系统、实时大数据处理)来说,是革命性的。当然,它们也并非没有代价,例如对CPU和内存带宽的消耗可能会略高,并且在某些特定场景下可能需要更精细的调优。

选择合适的JVM垃圾回收器:针对不同应用场景的决策与调优考量

选择一个合适的垃圾回收器,就像是为你的汽车选择合适的轮胎,得看路况和驾驶习惯。没有万能的答案,只有最适合你应用场景的那个。

法语写作助手
法语写作助手

法语助手旗下的AI智能写作平台,支持语法、拼写自动纠错,一键改写、润色你的法语作文。

法语写作助手31
查看详情 法语写作助手

在做决策时,我通常会考虑以下几个核心因素:

  1. 应用类型和性能目标

    • 吞吐量优先型(Throughput-oriented):如果你跑的是批处理任务、大数据分析,或者不需要实时响应的后台服务,那么你可能更关心单位时间内能处理多少数据,而不是单次请求的响应时间。这种情况下,可以考虑使用ParallelGC(JDK 8默认)或G1。它们在保证较高吞吐量的同时,也能提供相对可接受的停顿。
    • 延迟优先型(Latency-sensitive):如果你在做Web服务、API网关、实时交易系统、游戏服务器,或者任何对用户体验响应时间有严格要求的应用,那么GC停顿必须尽可能短。这时候,G1CMS(虽然有些过时,但特定场景仍有人用)、以及最新的ZGCShenandoah就是你的首选。
  2. 堆内存大小

    • 小到中等堆(几百MB到几个GB):对于这类堆,ParallelGC通常表现不错,因为它实现简单,吞吐量高。G1也可以胜任,并且能提供更好的停顿控制。
    • 大到超大堆(几十GB到TB级别):当堆内存达到这个量级时,传统的Full GC停顿会变得无法接受。G1是很好的通用选择,因为它能有效地管理大堆。而如果对延迟有极致要求,ZGCShenandoah则是几乎唯一的选择,它们能够将GC停顿时间稳定在个位数毫秒,即便堆很大。
  3. 对象分配和晋升速率

    • 如果你的应用频繁创建大量临时对象(高分配速率),并且这些对象很快就死亡,那么新生代的GC会很频繁。这种情况下,确保新生代有足够的大小,并且复制算法能高效工作很重要。
    • 如果大量对象存活时间长,或者晋升到老年代的速度很快,那么老年代的GC压力就会增大。这时就需要关注老年代收集器的效率和停顿。
  4. 硬件资源

    • CPU核心数:并发GC器(如CMS、G1、ZGC、Shenandoah)需要更多的CPU核心来支持GC线程与应用线程的并发执行。如果CPU资源有限,可能需要权衡。

调优策略的一些考量点:

  • JVM参数是关键:不要盲目调优,但了解常用的GC参数是必要的。
    • -Xms-Xmx:设置堆的初始和最大大小。通常建议设为相同值,避免运行时堆的动态扩展和收缩带来的额外开销。
    • -XX:+UseG1GC:启用G1收集器。
    • -XX:MaxGCPauseMillis=200:为G1设置目标停顿时间。
    • -XX:NewRatio-Xmn:调整新生代和老年代的比例或直接指定新生代大小。这对于控制新生代GC频率和对象晋升速度很有用。
    • -XX:+PrintGCDetails-XX:+PrintGCDateStamps:开启详细GC日志,这是GC调优的基石。没有日志,一切都是盲猜。
  • 监控和分析:使用JMX、VisualVM、Arthas、GCViewer等工具来监控GC行为、分析GC日志。通过观察GC频率、停顿时间、内存使用趋势,才能找到瓶颈所在。
  • 从小步快跑:不要一次性调整太多参数。每次只改动一个或一组相关参数,然后观察效果。
  • 压力测试:在生产环境部署前,务必进行充分的压力测试,模拟真实负载,观察GC行为是否符合预期。

总之,选择和调优JVM垃圾回收器是一个持续迭代的过程。它需要你理解应用本身的特性,结合GC算法的原理,通过参数调整和数据分析,最终找到一个最适合你系统的平衡点。这就像是打磨一件工具,越了解它,越能让它发挥出最大的效用。

以上就是说一下jvm 有哪些垃圾回收算法?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号