都叫“分代”,ZGC 和 G1 根本不是一回事:实测停顿差 1300 倍
同一套 Renaissance 基准,分代 ZGC 的停顿次数是 G1 的 6 倍,平均停顿却只有 G1 的 1/1300、最大停顿只有 1/2500;堆从 2G 涨到 5G,G1 最大停顿涨了 53%,ZGC 仍是 0.03ms。差别不只在参数:G1 分代是为了缩短 STW,ZGC 分代是为了降低并发回收成本——逻辑分代、并发收集年轻代、染色指针布局反转。
共 10 篇。每一篇都从真实的线上问题出发,给出可复现的排查路径。
同一套 Renaissance 基准,分代 ZGC 的停顿次数是 G1 的 6 倍,平均停顿却只有 G1 的 1/1300、最大停顿只有 1/2500;堆从 2G 涨到 5G,G1 最大停顿涨了 53%,ZGC 仍是 0.03ms。差别不只在参数:G1 分代是为了缩短 STW,ZGC 分代是为了降低并发回收成本——逻辑分代、并发收集年轻代、染色指针布局反转。
多个 TaskManager 结构相同、数据相近,CPU 使用率却差 10%。代码和数据差异都被排除后,剩下的解释只能在 JIT 编译状态与内存布局上——这类问题用业务指标永远看不出来。
JFR 是 JDK 自带的一站式采集器,但它的堆栈默认不是火焰图形态。把它转成火焰图要处理事件类型选择(cpu / wall / alloc)、帧折叠与权重口径三件事,本文给出可直接用的参数与工具。
程序同时跑 CPU 与 GPU 代码时,单独看任何一侧的火焰图都会误判瓶颈。混合采样的难点不在画图,而在两条采样流的时间对齐与损耗控制——采样本身上限决定了工具能不能上生产。
进程在 YGC 里 SIGSEGV 挂掉,堆栈指向 G1 的 copy_to_survivor_space——看起来像 GC 的 bug。换收集器仍然崩,才说明 JVM 对象模型已被破坏。unsafe 的错误写入不会立刻报错,直接原因与根因的现场差距可以非常大。
Java 与 C/C++ 混合调用场景下,perf、bcc、gdb 这些系统工具默认看不懂 Java 栈。把 JVM 的符号与栈展开接进 Linux 工具链,才能在不改业务代码的前提下做跨语言排查。
白天有流量、夜间几乎空闲的服务,堆内存一直占着不放。这需要区分「JVM 已回收」和「操作系统已回收」两件事,G1 的周期性 uncommit 与 region 归还策略决定了后者什么时候发生。
heapdump + MAT 是堆泄漏的常规手段,但在百 G 级堆上它本身就不可行:dump 要额外同等内存、解析要数小时、且会 STW。这篇给出一条在超大堆上仍然可用的替代路径。
pmap 里出现大量接近 64M 的内存块,就断定是 glibc 的 arena 作祟并去调 MALLOC_ARENA_MAX——这个结论跳过了验证。64M 是 malloc 的 mmap 分配阈值,不是泄漏证据,误判会把排查引向错误方向。
用 padding 填充 long 字段隔离两个 volatile 变量,实测却毫无提升——因为填充解决的是「同一缓存行内不同字段」的竞争,而伪共享的判定要退到 MESI 协议与缓存行对齐去看。这篇给出可复现的判定顺序。