2026-10-31·by Sijie Wang

JVM Memory Model and GC

JVM 内存模型与 GC:内存归谁管,死了怎么收

动机。 Java 的内存不像 C 那样手动管,也不像 Go 那样编译器加运行时一把梭,JVM 替你管分配和回收。但替你管不等于不用懂,OOM、GC 停顿、内存泄漏查起来,全靠这张图。

运行时数据区,分线程私有和共享两半。 线程私有的,每条线程一份:程序计数器(当前字节码行号,唯一一个不抛 OutOfMemoryError 的区)、虚拟机栈(一个方法一个栈帧,帧里是局部变量表加操作数栈)、本地方法栈(native 方法用的,跟虚拟机栈一个意思)。线程共享的,所有线程共用:堆(对象实例和数组都在这儿,分代,下面讲)、方法区(类元数据、常量池;JDK 8 之后叫元空间,用本地内存,不在堆里了,老的永久代 PermGen 被移除了)。

GC 的核心就两问:哪些死,死了怎么收。 哪些死用可达性分析:从 GC Roots 出发,能顺着引用链找到的就是活的,找不到的就是死的。GC Roots 包括栈帧里的局部变量、静态字段、常量引用、JNI 引用这几类。

死了怎么收靠分代假说:绝大多数对象朝生夕死,活得久的越活越久。所以堆分年轻代和老年代。年轻代是 Eden 加两个 Survivor(from 和 to),新对象先进 Eden,Eden 满了做一次 Minor GC,活下来的在两个 Survivor 之间复制来回倒,年龄够了(默认 15 岁,也有动态年龄判定)就晋升老年代。老年代满了做 Major GC,回收算法是标记清除或者标记整理。回收的时候要 STW(Stop-The-World,全拼就是“停止世界”,所有应用线程暂停),停顿时间就是这么来的。

收集器只列一张地图。 Serial(单线程,服务端今天基本不用)、Parallel(吞吐量优先,JDK 8 默认)、G1(JDK 9 之后默认:堆切成很多分区、混合回收)、ZGC 和 Shenandoah(停顿优先:着色指针加读屏障,毫秒级停顿)。跟 Parallel 的 trade-off 是:Parallel 只求总 GC 时间最短,不管单次停顿多长;G1、ZGC 用更多的总 CPU(并发标记、屏障、分区管理)换更短的单次停顿。所以批处理、离线任务这种停顿不敏感的场景,Parallel 天生就赢;G1 在疏散失败时会回退 Full GC(堆太小、晋升太快、大对象多),ZGC 要求堆有 headroom(并发回收要跑赢分配),分代 ZGC 之前的版本(分代是 JDK 21 才来的)对短命对象多的场景要全堆并发标记。默认参数也会带偏直接对比:G1 默认 200ms 停顿目标(MaxGCPauseMillis)让它频繁做小回收、牺牲吞吐,Parallel 默认不设停顿目标。

跟 Go GC 对一下。 Go 是一套算法走天下:三色标记加写屏障、两端 STW;JVM 是分代加多收集器可选,同一问题两种答案。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →