
1. G1 垃圾收集器不是“更快的CMS”而是为可控停顿而生的内存管理范式G1 垃圾收集器全称 Garbage-First是 Java HotSpot VM 自 JDK 7u4 起正式商用、JDK 9 成为默认 GC 的核心组件。它不是简单地把 CMS 换个名字再提速而是一次对 JVM 内存管理底层逻辑的重构——目标非常明确在堆内存达到数 GB 甚至数十 GB 规模时依然能将单次 GC 停顿Stop-The-World稳定控制在 200ms 以内且不以牺牲吞吐量为代价。这背后是它彻底抛弃了传统分代收集器“年轻代/老年代”物理隔离的刚性结构转而采用基于区域Region的堆内存划分 可预测停顿模型 并发标记与增量整理三位一体的设计哲学。你在网上搜到的“g1 主观赋权法”其实是个误传没有这个官方术语真正起作用的是 G1 的动态优先级排序机制——它会实时估算每个 Region 的回收价值即“垃圾比例 × 回收耗时预期”并按价值从高到低排列每次 GC 只选最“划算”的若干 Region 进行回收这就是“Garbage-First”名称的由来。而“gc(g1 old,all)是什么意思”这类问题本质是对 G1 回收范围的误解G1 不再有严格意义上的“Old GC”它只有一种统一的 Mixed GC 模式当老年代 Region 被标记为可回收后就会和部分年轻代 Region 一起打包进下一轮 Mixed GC不存在单独触发“老年代全回收”的指令。至于“com.android.systemui heaptaskdaemon 影响gc”这是 Android 系统层面对 G1 行为的定制化干预普通 Java 应用无需关注但提醒我们G1 的行为高度依赖 JVM 参数与运行时负载特征不是配完-XX:UseG1GC就万事大吉。如果你正被 JVM 面试题反复拷问“G1 和 CMS 有什么区别”或者在生产环境里因 GC 停顿抖动导致接口超时、订单丢失又或者在调优时发现-Xmx32g的服务Young GC 还算稳但一触发 Mixed GC 就卡顿 800ms——那说明你还没真正看懂 G1 的心跳节律。它不像 Serial 那样简单粗暴也不像 ZGC 那样追求极致低延迟G1 是工程师思维的产物用可量化的停顿目标倒逼整个回收流程的精细化调度。接下来我们就一层层剥开它的内核不讲教科书定义只说它在真实服务器上怎么呼吸、怎么决策、怎么避免把你拖进凌晨三点的告警漩涡。2. G1 的整体设计思路用“区域化”打破分代枷锁用“预测模型”驯服不确定性2.1 为什么必须放弃传统的“连续堆固定代际”结构先看一个典型痛点某电商订单服务堆内存设为-Xmx16g使用 Parallel GC。高峰期 Young GC 每 2 秒一次每次 30ms没问题但一旦促销开始大量订单对象晋升到老年代老年代占用率从 30% 快速爬升至 85%触发 Full GC。Parallel Old GC 开始扫描整个 12GB 老年代空间单次停顿长达 1.2 秒——用户下单按钮点击后 1.2 秒无响应支付超时率飙升。根本原因在于传统分代收集器的老年代是一整块连续内存GC 时必须遍历所有存活对象做标记对象越多、碎片越重扫描时间越不可控。G1 的破局点就是把这块“不可控的大蛋糕”切成无数块“可控的小切片”。它将整个 Java 堆无论大小划分为多个大小相等的Region每个 Region 通常是 1MB4MB由 JVM 根据堆总大小自动计算例如 4GB 堆默认 2MB/Region。关键在于这些 Region物理上连续逻辑上完全独立。一个 Region 可以充当 Eden另一个可以是 Survivor第三个可以是 Old第四个甚至可以是 Humongous存放超大对象如 1MB 以上的 byte[]。这种设计带来三个质变回收粒度从“代”降维到“Region”不再需要扫描整个老年代只需评估哪些 Region 垃圾最多、回收收益最高精准打击。空间分配从“连续寻址”变为“自由拼接”Eden 区不再需要一大片连续内存JVM 只需从空闲 Region 列表中挑几个出来即可极大缓解内存碎片压力。停顿时间从“看天吃饭”变为“可配置目标”G1 的核心参数-XX:MaxGCPauseMillis200不是承诺值而是调度目标。它会根据历史 GC 数据如各 Region 回收耗时、对象晋升速率动态调整每次 Mixed GC 所选 Region 的数量和类型确保平均停顿逼近该目标。提示G1 的 Region 大小在 JVM 启动时就固定无法运行时调整。若应用大量创建 1.5MB 的缓存对象而 Region 设为 1MB则每个对象独占 2 个 Region造成严重浪费。此时应通过-XX:G1HeapRegionSize2M手动指定 Region 大小让大对象能恰好填满一个 Region。2.2 G1 的三大核心阶段如何实现“并发标记”与“增量整理”的协同G1 的完整 GC 周期分为四个阶段但真正影响应用线程的是其中两个 STWStop-The-World环节其余均为并发执行初始标记Initial MarkSTW极短通常 5ms。仅标记从 GC Roots 直接可达的对象如线程栈、静态变量并标记这些对象所在 Region 为“存活”。这步快是因为它不遍历对象图只做根节点快照。并发标记Concurrent Marking完全并发应用线程与 GC 线程并行。G1 的标记线程会遍历 Initial Mark 标记出的存活对象逐层向下扫描其引用链同时记录每个 Region 的存活对象总数与垃圾占比。此阶段会产生“三色标记”中的“灰色”已标记但未扫描子对象和“黑色”已完全扫描对象。关键创新在于“SATBSnapshot-At-The-Beginning写屏障”当应用线程修改对象引用如obj.field newObject时写屏障会将被覆盖的旧引用oldValue记录到一个缓冲区SATB Buffer供后续并发标记线程检查——这保证了即使标记过程中对象图发生变更也不会漏标。最终标记RemarkSTW中等时长通常 2050ms。处理 SATB Buffer 中的残留记录并完成标记的精确修正。这是 G1 唯一需要全局暂停来“收尾”的环节。清理与复制Cleanup Evacuation分为并发清理Concurrent Cleanup和混合回收Mixed GC两部分。前者并发统计各 Region 的垃圾占比后者才是真正的“干活”阶段G1 选出一批高价值 Region主要是垃圾多的老年代 Region附带少量年轻代 Region在 STW 下将其中存活对象复制到新的空闲 Region 中同时整理内存、消除碎片。这才是 G1 实现“标记-整理”而非“标记-清除”的关键——它不直接在原 Region 上覆写而是搬迁天然压缩空间。注意G1 的“整理”是增量式、选择性的。它不会一次性整理整个老年代而是每次 Mixed GC 只整理一部分 Region。这正是它能兼顾低延迟与高吞吐的秘诀用多次小停顿替代一次大停顿。2.3 G1 的“主观赋权”真相回收价值模型如何动态计算网络热词“g1 主观赋权法”纯属误读G1 的决策依据是客观、可计算的回收价值模型。其核心公式为RegionValue (RegionTotalBytes - RegionLiveBytes) / RegionEvacuationTimeMsRegionTotalBytesRegion 总大小如 2MBRegionLiveBytes并发标记阶段估算出的该 Region 存活对象字节数RegionEvacuationTimeMsG1 根据历史数据预测的将该 Region 存活对象复制到新 Region 所需的时间考虑对象大小、跨 Region 引用复杂度等G1 维护一个按RegionValue降序排列的 Region 列表。每次 Mixed GC 开始前它会查询当前堆内存使用率、老年代占用率、历史 GC 停顿时间根据-XX:MaxGCPauseMillis目标反推本次允许的最大 evacuation 时间从列表顶端开始累加 Region 的RegionEvacuationTimeMs直到总和逼近目标时间将这些高价值 Region 打包发起本轮 Mixed GC。这意味着一个刚被大量对象填满、但其中 90% 是垃圾的 Region价值极高而一个只有 10% 垃圾、但对象引用关系极其复杂的 Region价值可能很低会被延后处理。这种动态调度让 G1 在面对不同业务负载如 OLTP 短事务 vs OLAP 长计算时都能找到最优回收路径。3. G1 的核心细节解析参数、日志、内存布局与实操陷阱3.1 关键参数详解不是堆越大越好而是“配得越准停顿越稳”G1 的参数体系远比 Parallel GC 复杂但核心就五个配错一个效果天壤之别-XX:UseG1GC启用 G1这是前提但绝非终点。-XX:MaxGCPauseMillis200最常被滥用也最易被误解的参数。它不是 SLA而是 G1 的“努力方向”。如果设为 50ms而你的机器 IO 或 CPU 已饱和G1 会强行减少每次回收的 Region 数量导致老年代持续增长最终触发 Full GC此时停顿可能达数秒。实测经验生产环境建议设为 200300ms给 G1 留出合理调度空间。-XX:G1HeapRegionSize2MRegion 大小。默认由 JVM 计算堆大小 / 2048但必须手动校准。判断依据查看 GC 日志中Humongous Allocation的频率。若频繁出现说明 Region 太小大对象被迫拆分或直接进入 Humongous 区加剧碎片。此时应增大此值原则是RegionSize 1.5 × 应用最大常规对象大小。-XX:G1NewSizePercent20与-XX:G1MaxNewSizePercent40年轻代占堆比例的上下限。G1 不固定年轻代大小而是动态伸缩。设为 20/40意味着年轻代可在 3.2GB6.4GB16GB 堆间浮动。关键技巧若 Young GC 频繁 1s 一次说明下限设太低应提高G1NewSizePercent若 Mixed GC 过于激进每 2 分钟一次说明上限太高年轻代吃掉太多空间应降低G1MaxNewSizePercent。-XX:G1MixedGCCountTarget8与-XX:G1MixedGCLiveThresholdPercent85控制 Mixed GC 的节奏。前者表示 G1 希望用 8 次 Mixed GC 清完所有可回收的老年代 Region后者表示只有当 Region 存活对象 ≤ 15% 时才将其纳入 Mixed GC。避坑点不要盲目调低G1MixedGCLiveThresholdPercent如设为 50这会导致大量“半满”Region 被过早回收复制成本飙升反而拉长停顿。实操心得我曾在一个实时风控服务上将MaxGCPauseMillis从 100ms 强行压到 50ms结果 GC 日志显示G1 Evacuation Pauses平均停顿确实降到 48ms但Full GC每小时发生 3 次。回滚后设为 250msFull GC 彻底消失平均停顿 210ms业务成功率反而提升 0.3%。G1 的智慧在于接受“可控的延迟”而非追求“绝对的低延迟”。3.2 GC 日志解码读懂 G1 的“体检报告”比调参更重要开启详细 GC 日志-Xlog:gc*,gcheap*,gcergo*,gcagedebug:file/path/to/gc.log:time,tags,levelJDK 10。关键字段解读[GC pause (G1 Evacuation Pause) (young)纯 Young GC只回收年轻代 Region。[GC pause (G1 Evacuation Pause) (mixed)Mixed GC回收年轻代 部分老年代 Region。[GC pause (G1 Evacuation Pause) (full)灾难性的 Full GCG1 回退到 Serial GC必须杜绝。Eden: 1234M(1234M)-0B(1234M)Eden 区使用量变化括号内为容量。Survivor: 123M-245MSurvivor 区对象晋升量若持续增长说明对象寿命变长可能需调大 SurvivorRatio。Heap: 8192M-3245M(16384M)堆内存变化8192M是 GC 前已用3245M是 GC 后已用16384M是总容量。Metaspace: 123456K-123456K(1126400K)元空间变化若used持续上涨可能是类加载泄漏。evacuation failed复制失败意味着目标 Region 空间不足或跨 Region 引用过于复杂。这是 Mixed GC 停顿飙升的前兆需立即检查G1HeapRegionSize和G1MaxNewSizePercent。一份健康 G1 日志的特征Young GC 频率稳定如每 13 秒一次停顿 50msMixed GC 间隔合理如每 515 分钟一次停顿在MaxGCPauseMillis± 20% 范围内evacuation failed字样零出现Full GC行数为 0。3.3 G1 内存布局实战Region 的七种角色与 Humongous 对象的隐形杀手G1 堆内存由数千个 Region 构成每个 Region 根据当前状态扮演不同角色Region 类型特征占比关键影响Eden新对象分配区GC 后清空动态通常 30%50%Young GC 频率直接由 Eden 大小决定Survivor存放 Young GC 中幸存的对象经历多次 GC 后晋升通常 5%10%SurvivorRatio参数影响其大小过小导致过早晋升Old存放晋升对象是 Mixed GC 的主要回收目标初始为 0随晋升增长老年代占用率 45% 是 Mixed GC 触发阈值Humongous存放 ≥ ½ RegionSize 的超大对象如大数组、缓存块可能 1%20%最大陷阱Humongous Region 不参与常规 GC只能等整个 Region 变成垃圾才回收极易导致内存浪费与 Full GCFree空闲 Region等待分配动态若长期低于 10%说明堆压力过大ArchiveJDK 10 引入存放不可变类数据如 bootstrap classes固定很小与业务无关可忽略Unused已分配但未使用的 Region0正常Humongous 对象的识别与治理在 GC 日志中搜索humongous allocation。若发现Allocated 123456 bytes as humongous for ...说明有对象超过G1HeapRegionSize/2。解决方案代码层拆分大对象如将 10MB 缓存数组改为 10 个 1MB 的 ListJVM 层增大-XX:G1HeapRegionSize让大对象能恰好填满一个 Region架构层将超大对象移出堆内存改用 off-heap如 Netty 的 PooledByteBufAllocator或磁盘存储。实操案例某推荐系统日志模块因打印完整 JSON 请求体产生大量 512KB 的 String 对象。RegionSize 默认 1MB512KB 500KB1MB/2本不该是 Humongous。但 String 内部 char[] 在 JDK 8 中是 UTF-16 编码512KB String 对应 256KB char[]仍安全。问题出在 JDK 11 的 Compact Strings 优化String 底层可能用 byte[] 存储 Latin-1 字符此时 512KB String 可能对应 512KB byte[]刚好踩在 1MB Region 的 50% 边界上被判定为 Humongous。最终方案升级 JDK 后将G1HeapRegionSize设为 4M彻底规避。3.4 G1 与 JVM 内存模型的深度耦合为什么 Metaspace 和 CodeCache 也会影响 GC很多人以为 G1 只管堆内存这是巨大误区。JVM 内存模型中Metaspace元空间和 CodeCache代码缓存的异常会直接诱发 G1 的 Full GC。Metaspace 泄漏动态生成类如 Spring CGLIB、MyBatis Mapper过多或 ClassLoader 未正确释放导致 Metaspace 持续增长。当 Metaspace 达到-XX:MaxMetaspaceSize限制时JVM 会触发 Full GC 来尝试卸载无用类。对策监控jstat -gc pid中MUMetaspace Used列若持续上涨用jmap -histo:live pid查看类加载器分布。CodeCache 溢出JIT 编译的热点代码过多填满 CodeCache默认 240MB。此时 JVM 会停止 JIT 编译并可能触发 Full GC。对策-XX:ReservedCodeCacheSize512m适当增大或-XX:-TieredStopAtLevel1关闭 C2 编译器牺牲性能换稳定。Direct Memory 泄漏ByteBuffer.allocateDirect()分配的堆外内存虽不属堆但其 Cleaner 对象在堆内。若 Direct Memory 泄漏Cleaner 队列积压会拖慢 G1 的并发标记阶段间接拉长 Mixed GC 停顿。对策-XX:MaxDirectMemorySize2g严格限制并用jcmd pid VM.native_memory summary监控。注意jvm gc回收器、jvm调优工具如 jstat、jmap、VisualVM它们看到的只是堆内存视图。要全面诊断 G1 问题必须结合jstat -gc pid看 Metaspace、jstat -compiler pid看 CodeCache、jcmd pid VM.native_memory看 Direct Memory形成四维监控矩阵。4. G1 的实操过程从零部署、参数调优到线上故障排查全流程4.1 新服务上线G1 的最小可行配置与基线测试不要一上来就堆砌 20 个参数。新服务部署 G1遵循“三步走”第一步基础启用与基线采集# JVM 启动参数JDK 8u261 或 JDK 11 -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -Xms8g -Xmx8g \ -XX:PrintGCDetails -XX:PrintGCTimeStamps \ -Xloggc:/opt/app/logs/gc.log运行 24 小时用jstat -gc pid 5000每 5 秒采样记录Young GC 频率与平均停顿Mixed GC 频率与平均停顿堆内存使用率趋势重点关注老年代占用率是否缓慢爬升第二步Region 大小校准分析 GC 日志搜索humongous allocation。若存在计算最大 Humongous 对象大小grep humongous gc.log | awk {print $8} | sort -n | tail -1 # 输出类似1234567 - 约 1.2MB则设置-XX:G1HeapRegionSize2M取略大于 1.2MB 的 2 的幂次方重启服务再观察 12 小时。第三步年轻代弹性调优若 Young GC 过于频繁 1s 一次说明 Eden 太小# 先临时加大年轻代下限 -XX:G1NewSizePercent30若 Mixed GC 过于密集 5 分钟一次说明年轻代吃掉太多空间老年代“饿”得快# 临时收紧年轻代上限 -XX:G1MaxNewSizePercent35每次调整后至少观察 4 小时对比jstat数据变化。实操心得我给一个新上线的物流轨迹服务配 G1初始-Xmx4gMaxGCPauseMillis200。基线测试发现 Young GC 每 800ms 一次停顿 25msMixed GC 每 18 分钟一次停顿 180ms。一切健康。但上线第三天监控显示老年代占用率从 20% 突然跳到 65%Mixed GC 频率飙升至每 2 分钟一次。查日志发现humongous allocation频繁。原来轨迹点数据序列化后单条消息达 1.8MB。将G1HeapRegionSize改为 4M 后问题消失。G1 调优的第一敏感点永远是 Humongous 对象而不是停顿目标。4.2 生产环境深度调优应对流量洪峰与内存泄漏的组合拳生产环境 G1 调优核心是“预判 监控 快速干预”。预判为大促准备 GC 安全垫提前一周用压测工具模拟 3 倍峰值流量观察 GC 行为若 Mixed GC 停顿逼近MaxGCPauseMillis的 90%则将-XX:MaxGCPauseMillis临时上调至 300ms给 G1 更宽松的调度空间增加-XX:G1ReservePercent20预留 20% 堆空间作为“安全气囊”防止 Mixed GC 期间无空闲 Region 可用设置-XX:G1HeapWastePercent5允许最多 5% 的堆空间被标记为“浪费”避免为清理最后一点垃圾而触发 Full GC。监控构建 G1 健康度仪表盘关键指标全部可通过 JMX 或 Prometheus Exporter 采集G1YoungGenerationCountYoung GC 次数/分钟 60 次/分钟需预警G1MixedGenerationCountMixed GC 次数/分钟 5 次/分钟需预警G1OldGenerationSize老年代已用内存 堆总大小的 70% 需预警G1HumongousObjectsHumongous 对象数量持续增长需排查G1EvacuationInfo各 Region 的回收效率EvacuationFailureRate 0.1% 即危险。快速干预三板斧止血当线上出现 GC 告警如 Mixed GC 停顿 500ms第一反应jstat -gc pid 1000 5连续采样 5 次确认是否偶发还是持续第二反应若确认恶化立即执行jcmd pid VM.native_memory summary scaleMB检查 Direct Memory 是否暴涨第三反应若 Direct Memory 正常执行jmap -histo:live pid | head -20看是否有异常类实例暴增如java.util.HashMap实例数翻倍指向内存泄漏。实操案例某支付网关在双十一流量高峰Mixed GC 停顿从 200ms 暴涨至 1200ms。jstat显示G1OldGenerationSize在 10 分钟内从 4GB 涨到 7GB。jmap -histo发现com.alipay.xxx.PaymentContext实例数达 200 万。排查代码发现 Context 对象被静态 Map 缓存且未设置过期策略。紧急上线修复同时将G1ReservePercent从 10% 提至 20%为修复窗口争取时间。G1 不是万能的它是放大镜会把代码里的内存问题以更尖锐的停顿形式暴露出来。4.3 G1 常见故障排查从日志到火焰图的全链路诊断故障一Mixed GC 停顿时间忽高忽低波动剧烈现象GC 日志中G1 Evacuation Pause (mixed)停顿时间在 100ms800ms 间随机跳跃无明显规律。排查路径jstat -gc pid 1000 10确认是否伴随G1OldGenerationSize快速上升jstack pid | grep RUNNABLE -A 5检查是否有线程在执行耗时 IO如慢 SQL、外部 HTTP 调用导致 GC 线程被抢占top -H -p pid看 GC 线程G1 Conc#0等CPU 使用率是否被其他线程压制终极手段用async-profiler生成 GC 期间的 CPU 火焰图./profiler.sh -e cpu -d 30 -f /tmp/gc-flame.svg pid若火焰图显示大量时间在Unsafe_GetObject或ObjectSynchronizer::inflate说明存在严重锁竞争GC 线程无法获得足够 CPU 时间片。根因与解决G1 的 Mixed GC 是 STW但其并发标记阶段依赖 CPU 资源。若应用线程 CPU 占用率长期 90%GC 线程得不到调度导致标记延迟最终在 STW 阶段需要处理更多待回收对象停顿飙升。对策限流降级非核心接口或增加机器 CPU 核数。故障二频繁evacuation failed随后触发 Full GC现象GC 日志中连续出现evacuation failed紧接着Full GC。根因分析G1HeapRegionSize过小导致大量 Humongous Region挤占空闲 RegionG1MaxNewSizePercent过高年轻代疯狂扩张留给老年代回收的空闲 Region 不足应用存在大量跨 Region 引用如一个大对象持有数百个散落在不同 Region 的小对象引用导致复制时目标 Region 空间不足。解决步骤立即检查G1HeapRegionSize若存在 Humongous按前述方法增大降低G1MaxNewSizePercent至 30%释放更多 Region 给老年代回收用jmap -dump:formatb,file/tmp/heap.hprof pid生成堆转储用 Eclipse MAT 分析Retained Heap最大的对象看其引用链是否异常复杂。注意evacuation failed是 G1 的“求救信号”不是错误。它意味着 G1 认为当前配置下无法安全完成回收主动放弃本次 Mixed GC等待下次。但若连续发生就会退化为 Full GC。预防胜于治疗日常监控G1EvacuationFailureRate是 SRE 的必修课。故障三G1 Old Generation占用率持续缓慢上升Mixed GC 无法有效回收现象G1OldGenerationSize每小时上涨 1%Mixed GC 后仅下降 0.2%老年代“只进不出”。根因锁定内存泄漏对象被静态集合、ThreadLocal、缓存等意外持有对象晋升阈值过低-XX:MaxTenuringThreshold1导致对象 1 次 Young GC 就晋升G1 的并发标记滞后应用分配速度远超 G1 标记速度导致大量老年代 Region 未被及时标记为可回收。验证与解决执行jmap -histo:live pid | head -20对比两次结果看哪些类实例数持续增长检查jstat -gc pid中G1MixedGenerationCount是否为 0若是说明 G1 认为老年代还不“饿”需调低-XX:G1MixedGCLiveThresholdPercent谨慎最有效手段强制触发一次并发标记周期jcmd pid VM.g1_run_final_mark然后观察 Mixed GC 是否恢复。5. G1 的边界与未来何时该放弃 G1拥抱 ZGC 或 ShenandoahG1 是成熟、稳健、适用面最广的 GC但它不是银弹。在以下场景G1 的“可控停顿”优势会迅速瓦解必须考虑替代方案5.1 G1 的硬伤大堆下的“标记-整理”成本天花板G1 的 Mixed GC 停顿时间与本次回收的 Region 数量 × Region 平均存活对象大小呈线性关系。当堆达到 64GB且业务对象普遍较大如金融风控中的复杂实体一次 Mixed GC 需搬运数 GB 存活对象即使只回收 10 个 Region停顿也可能突破 500ms。此时ZGC 的“着色指针 读屏障”架构能将停顿稳定在 10ms 内代价是 15% 的吞吐量损失和更高的内存占用约 16%。决策树堆 ≤ 16GB停顿要求 200ms → G1 是首选堆 1664GB停顿要求 10ms且能接受吞吐量下降 → ZGC堆 64GB或需要亚毫秒级停顿如高频交易→ ZGC 或 Shenandoah。5.2 G1 与 JVM 生态的协同演进从 JDK 8 到 JDK 21 的关键变迁JDK 8u202G1 稳定但G1ReservePercent默认 10%在大堆下易触发 Full GCJDK 9G1 成为默认 GC引入G1UseAdaptiveIHOP自适应初始堆占用预测减少早期 Mixed GCJDK 10G1NewSizePercent/G1MaxNewSizePercent默认值优化年轻代弹性更好JDK 11ZGC 发布实验性G1 开始面临挑战JDK 17ZGC 生产就绪G1 的“默认地位”被撼动JDK 21虚拟线程Project Loom普及大量短生命周期虚拟线程对象G1 的 Young GC 效率优势凸显但对老年代压力增大。个人体会我在 2023 年将一套 JDK 8 的风控系统升级到 JDK 17保留 G1。最大的收益不是停顿降低而是jstat中G1YoungGenerationCount下降了 40%——虚拟线程让对象生命周期更短大部分在 Eden 就被回收老年代压力骤减。G