JVM——垃圾回收
一 内存分配
Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。

新生代
大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。通过 Minor GC 之后,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区,如果 From 区不够,则直接进入 To 区。
老年代
以下几种情况对象会进入老年代。
**大对象直接进入老年代。**大对象直接进入老年代的行为是由虚拟机动态决定的,它与具体使用的垃圾回收器和相关参数有关。大对象直接进入老年代是一种优化策略,旨在避免将大对象放入新生代,从而减少新生代的垃圾回收频率和成本。
**长期存活的对象将进入老年代。**虚拟机给每个对象定义了一个对象年龄(Age)计数器。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁,最大也为15,因为Age是4位),就会被晋升到老年代中。
此外,还有一个动态对象年龄,Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时,取这个年龄为新的晋升年龄阈值。
空间分配担保
JDK 6 Update 24 之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。
二 死亡对象判断
引用计数法
通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。
如果该对象被其它对象引用,则它的引用计数加 1,如果删除对该对象的引用,那么它的引用计数就减 1,当该对象的引用计数为 0 时,那么该对象就会被回收。
但是该算法无法解决循环依赖问题。
可达性分析算法
通过 GC Roots 作为起点,然后向下搜索,搜索走过的路径被称为 Reference Chain(引用链),当一个对象到 GC Roots 之间没有任何引用相连时,即从 GC Roots 到该对象节点不可达,则证明该对象是需要垃圾收集的。
GC Roots有:
- 虚拟机栈中的引用(方法的参数、局部变量等)
- 本地方法栈中 JNI 的引用
- 类静态变量
- 运行时常量池中的常量(String 或 Class 类型)
- 所有被同步锁持有的对象
三 垃圾收集算法
标记清除
- 效率问题:标记和清除两个过程效率都不高。
- 空间问题:标记清除后会产生大量不连续的内存碎片。
复制算法
可用内存变小:可用内存缩小为原来的一半。
不适合老年代:如果存活对象数量比较大,复制性能会变得很差。
标记整理
- 效率问题:效率上比复制算法差很多。
分代收集算法
分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。
根据对象存活周期的不同会将内存划分为几块,一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择“复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
四 垃圾收集器
Parallel Scavenge + Parallel Old
这是JDK1.8及之前的默认垃圾收集器。
Parallel Scavenge 收集器是使用标记-复制算法的多线程收集器,关注点是吞吐量(高效率的利用 CPU)。
Parallel Old 收集器使用多线程和“标记-整理”算法,Parallel Scavenge 收集器的老年代版本。
CMS**(Concurrent Mark Sweep)**
CMS(Concurrent Mark Sweep)垃圾收集器是第一个关注 GC 停顿时间(STW 的时间)的垃圾收集器。CMS 垃圾回收器在 Java 9 中已经被标记为过时(deprecated),并在 Java 14 中被移除。
CMS 收集器是一种 “标记-清除”算法实现的,运作过程分为四步:
初始标记: 短暂停顿,标记直接与 root 相连的对象(根对象);
并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
主要优点:
并发收集
低停顿
主要缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1(Garbage First)
**G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。**G1在Java 7 推出,在Java 9成为默认垃圾回收器。
特点:
它将堆内存分为多个大小相等的区域(Region),每个区域都可以是 Eden 区、Survivor 区或者 Old 区。可以通过
-XX:G1HeapRegionSize=n
来设置 Region 的大小,可以设定为 1M、2M、4M、8M、16M、32M(不能超过)。G1 有专门分配大对象的 Region 叫 Humongous 区,而不是让大对象直接进入老年代的 Region 中。在 G1 中,大对象的判定规则就是一个大对象超过了一个 Region 大小的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中,而且一个大对象如果太大,可能会横跨多个 Region 来存放。
G1 会根据各个区域的垃圾回收情况来决定下一次垃圾回收的区域,这样就避免了对整个堆内存进行垃圾回收,从而降低了垃圾回收的时间。
增量:G1 可以以增量方式执行垃圾回收,这意味着它不需要一次性回收整个堆空间,而是可以逐步、增量地清理。有助于控制停顿时间,尤其是在处理大型堆时。
并行:G1 垃圾回收器可以并行回收垃圾,这意味着它可以利用多个 CPU 来加速垃圾回收的速度,这一特性在年轻代的垃圾回收(Minor GC)中特别明显,因为年轻代的回收通常涉及较多的对象和较高的回收速率。
标记整理:在进行老年代的垃圾回收时,G1 使用标记-整理算法。这个过程分为两个阶段:标记存活的对象和整理(压缩)堆空间。通过整理,G1 能够避免内存碎片化,提高内存利用率。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。
ZGC
ZGC(The Z Garbage Collector)是 JDK11 推出的一款低延迟垃圾收集器,适用于大内存低延迟服务的内存管理和回收,停顿时间远胜于 G1 和 CMS。
特点:
- 全并发操作:几乎所有阶段(标记、转移、重定位)均并发执行,仅需极短 STW(通常 <1ms)。通过 染色指针(Colored Pointers) 和 读屏障(Load Barrier) 实现并发对象标记与移动。
- 无分代设计:基于 Region 的堆布局(动态调整 Region 大小,支持 2MB/32MB/…),单一代(无 Young/Old 代),但通过并发处理实现高效回收。
- 极短停顿时间:停顿时间不超过 10ms;停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
- 最大支持 16TB 的堆内存。