设想你是一家有 1000 个房间的大酒店经理。客人川流不息:有的住三天就退房,有的长住一年。每间房都会留下废弃的行李、过期的报纸、坏掉的家具——这就是“垃圾”。房间满了,新客人就进不来,于是你需要一支“清扫队”(GC)定期清理。 但清扫有代价:旧式酒店一打扫,就得让所有客人在走廊里原地站定,等打扫完才能继续走动——这叫“停顿世界(Stop-The-World,STW)”。停顿久了,客人体验极差。 于是酒店管理界出现了三位“剑客”: - G1:把酒店切成 2048 个小区,每次只挑最脏的小区扫,客人只需短暂停步(约 10ms)。 - ZGC:给每件行李贴彩色标签,清扫队趁客人转身的瞬间完成换房,客人几乎无感(< 1ms)。 - Shenandoah:搬家时在旧房间门口贴一张“已搬到 802”的告示,客人看到告示自己跟过去,同样几乎无感。 本篇就用这座“智慧酒店”的视角,带你认识三支清扫队的内部机制、调优手法与选型心法。
对 AI 从业者而言,模型推理、特征服务、向量检索大多跑在 JVM 上,GC 停顿直接决定 p99 延迟天花板。掌握三剑客,你就掌握了让在线 AI 服务既快又稳的底层密码。
可视化演示
展示G1 Region分区内存布局、G1/ZGC/Shenandoah停顿时间对比、GC算法选型依据。切换模式理解三种垃圾回收器的核心技术差异。
本节配有交互动效(进入视口后自动加载,约 3KB)。
G1 Region分区内存布局 G1将堆划分为大小相等的Region(默认约2048个),每个Region可动态切换为Eden/Survivor/Old Garbage-First策略优先回收垃圾最多的Region,实现可预测的停顿时间 -XX:MaxGCPauseMillis=200, Region=heap/2048 G1通过Region分区实现可预测停顿
GC背景与挑战
酒店越开越大,房间(堆内存)总会被住满。传统的“清场大扫除”有一个硬伤:清扫期间必须让所有客人在原地站定,任何人不能走动、不能取行李、不能办入住——这个全员静止的时刻就是“停顿世界”(Stop-The-World,STW)。清扫一分钟,酒店就停业一分钟,客人的怨气也就积累一分。
在 JVM 里,应用线程就是客人,GC 线程就是清扫队。堆中那些不再被引用的对象就是废弃行李,被引用的存活对象就是仍在使用的行李箱。GC 回收垃圾时,某些阶段必须暂停所有应用线程。停顿越久、次数越多,业务请求被“卡住”的时间就越长,尤其是高并发场景下,毫秒级的停顿都会被放大成可见的延迟毛刺。
衡量清扫队优劣,核心看两个指标:吞吐量(Throughput)和停顿时间(Pause Time)。吞吐量是指应用真正运行的时间占比——如果一次清扫 20ms,一天清扫 100 次,就相当于酒店每天“假性停业” 2 秒。对于延迟敏感的服务,更要盯住单次停顿时间和 p99 延迟。
G1、ZGC、Shenandoah 这三位“剑客”,本质上是三种不同的清场策略:有的缩小清扫范围,有的让清扫与营业并行,有的让客人自己跟着告示走。理解它们各自的取舍,就是本篇的主线。
- 应用运行时间酒店正常营业的总时长,即应用线程真正执行业务逻辑的时间。
- GC 停顿时间酒店闭门清扫的总时长,等于所有 STW 暂停的累加。
- 吞吐量占比营业时间占全天的比例,越高说明 GC 浪费的时间越少。
G1区域化架构
G1 的管理员做了一个关键决定:不再把整栋酒店当成一个必须全员停摆的大仓库,而是把它切成上千个大小相等的“小区”(Region),每个小区独立评估脏乱程度。清扫时不必全楼停业,只需在极短时间内处理几个小区,客人只感到“被叫停了一瞬”。
“Garbage First“(垃圾优先)这个名字正是由此而来:每次清扫前,管理员会估算每个小区的”垃圾量 / 清扫成本“比值,优先处理比值最高的小区。就像保洁主管拿着脏乱报表,专挑”垃圾最多又最好扫”的楼层先下手,而不是从一楼到顶楼无论脏净挨个扫。
要实现“只扫一个区”,难点在于:其他区的客人可能还引用着这个区里的行李。如果不清查全楼,搬走行李就会导致引用断裂。G1 用 RSet(记忆集)记录“有哪些外部区的引用指向本区”,清扫时只核对账本,不必全楼排查;并发标记阶段则用 SATB(起始快照)保证标记开始后新产生的对象也不会漏网。代价是维护这些结构需要额外内存与 CPU。
这种“分区治理 + 脏乱优先”的策略,让 G1 自 JDK 9 起成为官方默认回收器,也是今天绝大多数 Java 后端服务开箱即用的选择——它不追求极致,却足够均衡可靠。
- 垃圾量第 i 个 Region 中可回收垃圾对象占用的字节数,垃圾越多越值得扫。
- 回收成本清扫该 Region 需要复制存活对象、更新 RSet 的总工作量,存活对象越多成本越高。
- 价值排序每个周期把各 Region 的 P 值从大到小排序,优先回收 Top N 个小区。
G1调优实践
G1 提供了一个“期望停顿目标”参数 -XX:MaxGCPauseMillis(默认 200ms)。它的自适应调节器(Ergonomics)会依据历史 GC 数据,动态调整每轮回收多少个 Region,尽量让停顿不超过这个目标——就像保洁主管根据过去一周的清扫速度,决定这轮让保洁员扫几个楼层的房间。
大对象(Humongous)是 G1 的一个特殊难题:超过 Region 大小一半的对象(如超大数组、大缓存)会被放进连续的“巨物区”。巨物区回收时机被动,且容易造成堆碎片——好比酒店走廊里横着几个超大衣柜,保洁队只能绕着走,平时还搬不动。实践中可通过 -XX:G1HeapRegionSize 调节 Region 大小,让它与业务对象体量匹配。
另一个关键机制是混合回收(Mixed GC):当老年代占用率达到 IHOP(默认 45%)时触发并发标记,标记完成后 G1 把年轻代 Region 和“垃圾占比高”的老年代 Region 放在一起回收。调优时重点观察:并发标记周期是否过于密集、Mixed GC 是否频繁发生,以及 GC 日志中的停顿是否稳定。
但千万别把目标设成 1ms——就像要求保洁员每次只扫一个房间,客人确实只停一瞬,可一天被叫停几十次,整体体验反而更差。实践中 10~20ms 往往是最佳平衡点。
- 目标停顿用户通过 MaxGCPauseMillis 参数设定的期望最大停顿时间,单位毫秒。
- 单区平均回收耗时G1 根据历史 GC 记录统计出的平均清理一个 Region 的时间。
- 年轻代 Region 数量两者相除向下取整,决定下一轮 Young GC 收集的 Region 数,从而把停顿控制在目标内。
ZGC染色指针
ZGC 的酒店不切小区,而是给每件行李贴一张“颜色标签”。在 64 位指针中,低 42 位是真正的房间号(堆内地址),中间 4 位是颜色(M0、M1、Remapped、Finalizable),高位全部置零。颜色状态告诉清扫队:这件行李是旧的、新的,还是已经搬过家,从而实现在客人完全无感的情况下完成标记和搬迁。
读屏障(Load Barrier)是这套机制的执行者:客人每次弯腰取行李时,系统都会快速检查颜色标签。如果发现行李已被标记为“需要搬迁”,就当场顺着指针拿到新房间的地址。这意味着“搬迁”可以完全并发进行——清扫队在前面搬,客人照常在后面取,两不耽误。
ZGC 的完整流程分为并发标记、并发准备转移、并发转移等大阶段,STW 只出现在标记开始与结束的两次极小暂停中,停顿时间与堆大小基本无关。因此 ZGC 特别适合超大堆(TB 级)和极低延迟场景,JDK 15 起生产可用,JDK 21 引入分代 ZGC 后吞吐量进一步提升。
这就像酒店服务员在你递咖啡的瞬间完成换房——标签一变,客人手里的钥匙卡依然有效,真正的门牌号更新在后台悄悄完成,全程不到 1 毫秒。
- 地址位低 42 位可寻址 4TB(后续 JDK 版本更高),存放对象在堆中的真实偏移。
- 颜色标记位第 42~45 位共 4 个元数据位,分别表示 Marked0、Marked1、Remapped、Finalizable 状态。
- 保留位第 46~63 位必须全零,这也是指针染色合法性的前提。
ZGC调优要点
ZGC 的调优哲学是“少动参数,规划好堆”。由于并发转移要求“旧房间”和“新房间”同时存在,堆必须同时装下“存活对象”和“一个 GC 周期内新产生的对象”。如果空间不足,转移就会因放不下而退化,停顿随之飙升——好比搬家公司到了却发现新仓库只有一半大,只能把家具堆在走廊里。
并发线程数由 -XX:ConcGCThreads 控制,默认由 JVM 根据 CPU 核数和堆大小自动推算。并发线程过多会跟业务线程抢 CPU,导致吞吐量下降;过少则清扫速度跟不上分配速度,堆迅速膨胀。一般无需手工调整,除非你清楚机器的 CPU 余量。
JDK 21+ 引入分代 ZGC:把对象按年龄分成年轻代和老年代两个区域,年轻代频繁回收短命小对象,老年代很少被打扰。与不分代版本相比,分代 ZGC 的吞吐量提升显著,适合既想要 ZGC 低延迟、又在意吞吐的线上服务。
这就像未来酒店虽然清扫不打扰客人,但必须备好“两套房间”周转——床位规划(堆大小)比扫把数量(并发线程)更决定成败。
- 存活集一个 GC 周期开始时仍然存活的对象总量,是堆的“底仓”。
- 周期内新增分配应用分配速率乘上 GC 周期时长,是清理期间新产生的对象量。
- 堆容量下界两者之和构成堆容量的下限,规划 -Xmx 时必须留出余量。
Shenandoah并发疏散
Shenandoah 的酒店走了另一条技术路线:不贴标签,而是给每个对象预留一个 Brooks 指针——相当于在房间门口钉一块小牌子:“本房间行李已搬到 802”。客人取行李时,门前的保安(读屏障)扫一眼牌子:若显示已搬迁,就指引到新房间;若没有,则直接放行。
这套“转发指针”机制让对象搬迁可以完全并发:清扫队推着推车在走廊里把旧房间的行李一件件搬到新房间,与此同时客人照常住店、取物。整个疏散(Evacuation)阶段的暂停时间只与根集合大小相关,与堆大小基本无关——堆再大,停顿也不随之增长。
为了在并发搬迁后高效地更新所有指向旧地址的引用,Shenandoah 使用 连接矩阵(Connection Matrix):一个位图,记录哪两个 Region 之间存在引用关系。更新引用时只需处理“真正有联系的区域对”,不用全堆扫描,就像酒店管家手里有一张“谁认识谁”的关系图,通知搬家事宜只需通知相关楼层。
客人会察觉吗?最多一瞬间的疑惑:“咦,行李怎么不见了?”——抬头看到告示,顺着指引走过去,整个过程微不足道。这就是并发疏散的优雅之处。
- Brooks 指针每个对象头部预留一个字长的转发指针,指向搬迁后的新对象。
- 条件读取读屏障检查转发指针,据此决定是直接读取还是跳转到新地址。
- 并发收益搬迁全程并发执行,STW 仅保留在根扫描等极短阶段,且与堆大小无关。
Shenandoah调优要点
Shenandoah 提供多种启发式模式,通过 -XX:ShenandoahGCHeuristics 选择:adaptive(自适应,默认)根据分配速率动态调节 GC 周期;static(静态)按固定周期触发;compact(紧凑)适合堆很满、需要紧急回收的场景。选模式就像选保洁排班:客流量大时动态排班,淡季按固定时点扫,紧急大扫除集中突击。
堆规划同样至关重要。Shenandoah 的并发转移依赖足够的周转空间,若堆太满、疏散来不及完成,会触发 Degenerated GC(退化回收)甚至 Full GC,性能断崖式下跌。一般建议堆容量为存活集的 2~3 倍,并持续监控疏散完成率,确保并发转移始终跟得上分配速度。
适用场景上,Shenandoah 常见于中等规模堆(数十 GB 级别)+ 延迟敏感的服务,如交易、搜索、网关等。它在对象头上多了转发指针,每次访问多一次间接跳转,吞吐量略低于 ZGC,但调优参数更直观、内存布局更简单,社区评价“对运维更友好”。
就像叫搬家队之前先盘点家当、估算搬运速度,才能决定需要多大周转仓库——Shenandoah 的调优,本质是“先算账,再搬家”。
- 存活对象总量需要被疏散的存活对象字节总和,即待搬迁的家当总量。
- 并发回收速率清扫线程单位时间能疏散的字节数,取决于 CPU 核数与并发线程数。
- GC 周期耗时两者相除得到一次回收周期的大致时长,用于反推堆的周转空间需求。
三剑客选型对比
三位剑客,三种哲学:G1 是“性价比之王”——分区治理、脏乱优先,吞吐量最高,停顿在几十毫秒量级;ZGC 是“未来旗舰”——染色指针、读写无感,停顿亚毫秒、可支撑 TB 级超大堆;Shenandoah 是“务实新贵”——转发指针、连接矩阵,低延迟且配置直观,在中等规模堆上尤为顺手。
从数据看,G1 的停顿通常在 10~50ms(取决于堆大小与参数),吞吐量可达 99% 以上;ZGC 与 Shenandoah 的停顿低于 1ms 或在数毫秒内,但由于读屏障的 CPU 开销,吞吐量略低(通常也在 98% 上下)。堆规模上,ZGC 对超大堆更有优势,Shenandoah 在中等堆上更容易调优,G1 则通吃绝大多数场景。
选型建议可以浓缩为三句话:不知道选什么,就用 G1(JDK 9+ 默认,均衡可靠);压测发现 p99 被 GC 停顿拖垮、堆又大,上 ZGC;堆中等、想要低延迟且希望调参简单,上 Shenandoah。关键动作是压测先行——用 GC 日志分析工具量化停顿和吞吐,用数据代替直觉做决策。
选对清扫队,客人从不察觉深夜的酒店曾悄悄大变样;选错,则每天多出几秒“闭门谢客”。工程心法六个字:先测量,再选择。测量 GC 日志,明确延迟目标,然后让三剑客各归其位。
- G1 停顿量级约 10ms 上下,受堆大小、参数与负载影响。
- ZGC 与 Shenandoah 停顿量级两者均低于 1ms(Shenandoah 视配置可达数 ms)。
- 梯队对比用不等号直观呈现三者延迟差异,延迟敏感场景只有后两者能满足。
AI 应用场景
在线推理服务低延迟优化
阶段:模型部署与服务化
解决的问题:推理网关、特征服务是典型的延迟敏感型 JVM 服务。传统 G1 每次停顿 10~30ms,直接击穿 p99 延迟水位线,高峰时超时率上升。ZGC 的亚毫秒停顿让“酒店清场”几乎不可感知,p99 显著回落。
具体案例:某 LLM 对话服务的网关模块,堆 48GB,切换 ZGC 并预留足够周转空间后,STW 从平均 18ms 降到 0.6ms,p99 从 25ms 降至 8ms,超时率下降 60%。
离线批量特征加工吞吐优化
阶段:数据预处理与特征工程
解决的问题:批量任务对延迟不敏感,只关心 12 小时窗口内能否跑完。ZGC 的读屏障会额外消耗 CPU,反而拖慢这类“吞吐优先”的任务;保持 G1 并合理调大年轻代,能在吞吐上拿到最优解。
具体案例:某推荐系统的离线特征平台,Spark Executor 保持 G1 默认并设置 20ms 停顿目标,配合大页与年轻代调优,整体任务耗时缩短 12%,GC 停顿时间占比从 8% 降到 4%。
向量检索近线索引服务
阶段:检索与召回
解决的问题:向量检索服务需要在查询的同时平滑重建索引,GC 抖动会拉高召回超时。Shenandoah 的并发疏散让索引搬迁与查询并发执行,停顿与堆大小无关,适合中等堆的低延迟检索场景。
具体案例:某搜索系统的近线向量索引更新服务,堆 16GB,选用 Shenandoah adaptive 模式,索引重建期间查询 p99 稳定在 3ms,无一次因 GC 导致的超时。
Python 演示
def simulate_gc(name, pause_ms, interval_ms, total_ms): # 模拟单个GC策略: 每经过interval_ms触发一次停顿, 统计吞吐量 gc_count = 0 total_pause = 0.0 t = 0 while t < total_ms: t += interval_ms if t >= total_ms: break gc_count += 1 total_pause += pause_ms app_time = total_ms - total_pause throughput = app_time / total_ms * 100 return gc_count, total_pause, throughput def show_comparison(): configs = [ ("G1", 15.0, 6000), ("ZGC", 0.4, 12000), ("Shenandoah", 0.7, 10000), ] print(f"{'回收器':<12}{'GC次数':>8}{'总停顿ms':>10}{'吞吐量%':>10}") print("-" * 44) for name, pause, interval in configs: count, pause_sum, throughput = simulate_gc(name, pause, interval, 120000) print(f"{name:<12}{count:>8}{pause_sum:>10.1f}{throughput:>10.2f}") if __name__ == "__main__": show_comparison()
while t < total_ms:模拟一个完整运行周期,时间不断向前推进直到任务结束。gc_count += 1每到一次 GC 间隔就记录一次停顿发生,对应一次“酒店闭门清扫”。total_pause += pause_ms把该策略单次停顿时间累加,得到总停顿时长。app_time = total_ms - total_pause应用真正可用的时间 = 总时长 − 所有 GC 停顿。throughput = app_time / total_ms * 100吞吐率 = 应用可用时间占总时间的百分比。configs = [("G1", 15.0, 6000), ...]三支清扫队的参数:名称、单次停顿、触发间隔。
常见误区
误区听说 ZGC 暂停不到 1ms,就把所有服务都切到 ZGC,以为延迟问题全部解决。反正参数少,切过去就不用管了。
正解ZGC 的并发标记和读屏障要占用额外 CPU(通常带来约 5%~10% 的吞吐损失)。如果业务对延迟不敏感,比如离线批量任务,或对吞吐有强要求,G1 反而更好。选 GC 的第一步永远是明确目标:延迟优先还是吞吐优先,然后再选剑。
为何常见:只盯着“停顿时间”一个指标,忽略了并发屏障带来的 CPU 成本与更大的内存占用,属于典型的单指标决策。
误区既然 G1 有停顿目标参数,那把 `-XX:MaxGCPauseMillis=1` 设成 1ms,停顿不就一定小于 1ms 了吗?调优也太简单了。
正解MaxGCPauseMillis 只是“期望目标”而非“硬性承诺”。G1 通过缩小每轮回收的 Region 数量来逼近目标,设得太小会导致年轻代被压得极小,GC 频率暴增,整体吞吐和延迟反而恶化。实践中 10~20ms 是常见合理区间,且必须配合 GC 日志持续观测。
为何常见:把“调节器的目标参数”误当成“绝对的硬限制”,这是对 G1 自适应调节机制最常见的误解。
误区反正内存便宜,把 `-Xmx` 设到物理内存上限,GC 就不会频繁触发了,系统自然更快。
正解堆越大,标记和转移的扫描范围越大,G1 的单次停顿通常更长;ZGC 需要为并发转移预留周转空间,堆过大反而加重并发周期的内存压力。正确做法是先测量存活集,再按存活集的 2~3 倍留出余量,而不是盲目撑满物理内存。
为何常见:直觉上“空间多 = 少清理”,忽略了 GC 的扫描与复制成本与堆大小成正比这一事实。
误区都是并发收集、停顿都不到 1ms,那 Shenandoah 和 ZGC 随便选一个就行,没什么差别。
正解两者底层机制完全不同:ZGC 靠染色指针在指针里存元数据,转移时对象本身不动;Shenandoah 在对象头加 Brooks 转发指针,读屏障检查后跳转。由此带来内存布局、屏障成本、调优参数和适用堆规模的差异,选型时要区分看待。
为何常见:只从“停顿时间”这个结果看,两者高度相似,掩盖了底层实现机制的显著差异。
误区ZGC 停顿 0.5ms,G1 停顿 15ms,那 ZGC 的效率肯定比 G1 高,跑同样的任务一定更快。
正解停顿时间和吞吐量是两个正交的指标。并发收集器用额外 CPU 换取低停顿;G1 用可控停顿换取较少 CPU 开销。实测中 G1 的吞吐量往往略高于 ZGC。延迟敏感型业务看停顿,计算密集型业务看吞吐,两者不可混为一谈。
为何常见:把“用户体验好坏”(停顿)直接等同于“机器效率”(吞吐),混淆了延迟与吞吐这两个维度的评价标准。
面试题
Q1. G1 的 "Garbage First" 核心思想是什么?
- A.优先回收垃圾占比最高的区域← 正确答案
- B.优先回收年轻代中的对象
- C.优先回收体积最大的对象
- D.优先回收地址靠前的区域
答案:A。G1 每次清扫前按“垃圾量/回收成本”排序,优先回收垃圾最多、回收价值最高的 Region,因此得名 Garbage First。B 是年轻代回收的常规行为而非核心思想,C 和 D 与 G1 的选择策略不符。
Q2. ZGC 用染色指针将元数据存放在哪里?
- A.对象头中开辟的专用字段位
- B.指针中单独开辟的元数据位← 正确答案
- C.堆外独立的标记位图文件
- D.JIT 编译器生成的局部变量
答案:B。ZGC 在 64 位指针中单独划出第 42~45 位存放 M0/M1/Remapped/Finalizable 四个颜色位,而不是存进对象头或位图文件。这就像把行李标签贴在钥匙牌(指针)上而非行李箱(对象)上,读取时无需访问对象本身。
Q3. 下列属于 ZGC 并发执行阶段的有哪些?
- A.并发标记所有存活对象← 正确答案
- B.并发转移对象到新地址← 正确答案
- C.并发重映射所有旧引用← 正确答案
- D.完全停止应用的根扫描
答案:ABC。ZGC 的标记、转移、重映射都可以与应用线程并发执行,这正是它停顿 <1ms 的原因。D 的根扫描虽然很短,但属于 STW 阶段——就像酒店开门营业前,保安要花极短时间清点一下门口人员。
Q4. Shenandoah 读屏障依赖什么机制找到搬迁后的对象?
- A.对象头中的 Brooks 转发指针← 正确答案
- B.染色指针中的颜色元数据位
- C.全局哈希表记录新旧地址
- D.操作系统虚拟内存页映射
答案:A。Shenandoah 在每个对象头部预留 Brooks 转发指针,读屏障检查该指针是否为空,非空则跳转到新对象。ZGC 才用染色指针(B),Shenandoah 不用全局哈希表(C)和虚拟内存映射(D)。
Q5. G1 中 RSet 记忆集记录的主要内容是什么?
- A.外部 Region 指向本区的引用← 正确答案
- B.本 Region 内部对象年龄顺序
- C.GC 线程运行时的详细日志
- D.JIT 编译产生的热点方法表
答案:A。RSet 记录“谁在引用我”——即其他 Region 指向本 Region 的跨区引用。清扫一个 Region 时只需核对 RSet,不必全堆扫描。这就像酒店管家的小本子记着“哪些楼层的客人知道这个房间的行李”。
Q6. 关于三剑客的实现机制,下列说法正确的有哪些?
- A.G1 使用 RSet 维护跨区引用← 正确答案
- B.ZGC 依赖染色指针和读屏障← 正确答案
- C.Shenandoah 使用 Brooks 指针← 正确答案
- D.三种 GC 都完全避免 STW 停顿
答案:ABC。G1 的 RSet、ZGC 的染色指针+读屏障、Shenandoah 的 Brooks 指针都是各自的核心机制。但三者都无法完全消除 STW,只是把停顿压到极短。D 的错误在于“完全避免”过于绝对,连 ZGC 也有毫秒级以下的根扫描暂停。
Q7. 某 AI 推理服务堆 64GB,要求 p99 延迟小于 5ms,最合理的 GC 选择是?
- A.G1 默认参数直接运行
- B.ZGC 并配置合适的堆容量← 正确答案
- C.Shenandoah 强制关闭并发
- D.Serial GC 单线程串行回收
答案:B。64GB 堆 + 5ms 内的 p99 要求,G1 的 10ms 级停顿难以满足,Serial GC 会带来秒级停顿。ZGC 的亚毫秒停顿最适合超大堆低延迟场景。Shenandoah 理论上可行,但“强制关闭并发”(C)会使其丧失核心优势。
Q8. 对 G1 进行调优时,下列哪些实践是合理的?
- A.设置适度的 MaxGCPauseMillis 目标← 正确答案
- B.监控 IHOP 触发的并发标记周期← 正确答案
- C.用 RegionSize 匹配超大对象的分配← 正确答案
- D.关闭全部 GC 日志以节省开销
答案:ABC。设定合理停顿目标、监控 IHOP 并发标记、调节 RegionSize 匹配大对象都是常见且有效的 G1 调优手段。D 的错误在于 GC 日志开销极小,它是定位调优问题的基础依据,关闭日志得不偿失。
Q9. Stop-The-World(停顿世界)的含义是什么?
- A.应用线程全部暂停等待 GC 完成← 正确答案
- B.应用线程降低速度继续运行
- C.只暂停分配内存的新生线程
- D.GC 线程等待应用线程让出 CPU
答案:A。STW 指 GC 的某些阶段需要暂停所有应用线程,就像酒店大扫除时所有客人必须原地站定。B 的“降速运行”不是 STW,C 只暂停部分线程也不对,D 把等待方向完全搞反了。
Q10. 下列哪些垃圾回收器能够实现并发转移?
- A.ZGC 并发转移对象到新区← 正确答案
- B.Shenandoah 并发疏散对象← 正确答案
- C.Parallel Scavenge 并行回收
- D.Serial GC 串行回收对象
答案:AB。ZGC 的并发转移和 Shenandoah 的并发疏散都是“搬家不闭店”的典型代表。Parallel Scavenge 和 Serial GC 的转移阶段都需要 STW,属于传统“清场式”回收器,无法并发搬迁对象。