JVM垃圾回收详解
堆空间结构与内存分配回收原则
JVM 堆(GC 堆)是垃圾回收核心区域,基于分代收集思想划分:JDK1.7 及之前分为新生代(Eden 区、两个 Survivor 区 S0/S1)、老年代、永久代;JDK1.8 后永久代被元空间(本地内存实现)取代,新生代和老年代结构保持不变。内存分配遵循三大原则:对象优先在 Eden 区分配,Eden 区满则触发 Minor GC;大对象(需大量连续内存,如字符串、数组)直接进入老年代,避免新生代频繁 GC;长期存活对象(Survivor 区中每经历一次 Minor GC 年龄 + 1,默认年龄达 15 或动态年龄计算达标)晋升老年代,动态年龄计算指当某一年龄对象总大小超过 Survivor 区 50%,取该年龄与 MaxTenuringThreshold 最小值作为晋升阈值。
垃圾回收(GC)分类
GC 按回收范围分为 Partial GC(部分收集)和 Full GC(整堆收集):Partial GC 包括仅回收新生代的 Minor GC(Young GC)、仅回收老年代的 Old GC(仅 CMS 支持)、回收新生代 + 部分老年代的 Mixed GC(仅 G1 支持);Full GC 回收整个堆(新生代、老年代)和方法区,触发场景包括老年代空间不足、永久代 / 元空间不足、Minor GC 前晋升对象平均大小超老年代剩余空间等。空间分配担保机制确保 Minor GC 安全:JDK6 Update24 后,只要老年代连续空间大于新生代对象总大小或历次晋升平均大小,即执行 Minor GC,否则触发 Full GC。
死亡对象判断方法
判断对象是否可回收有两种核心算法:一是引用计数法,通过对象引用计数器增减判断,简单高效但无法解决循环引用问题,主流虚拟机未采用;二是可达性分析算法,以 GC Roots 为起点,沿引用链搜索,不可达对象判定为可回收,GC Roots 包括虚拟机栈局部变量表引用对象、本地方法栈 Native 方法引用对象、方法区静态属性 / 常量引用对象、同步锁持有的对象等。不可达对象并非必然回收,需经历两次标记:首次标记后筛选是否需执行 finalize () 方法,无需执行则直接回收,需执行则放入队列二次标记,若二次标记前未建立新引用则回收,finalize () 方法已逐渐被弃用。
引用类型(JDK1.2 后扩充)
Java 引用强度从强到弱分为四类:强引用(程序中普通赋值,如 String str = new String ("abc")),内存不足时虚拟机宁愿抛 OOM 也不回收;软引用(SoftReference),内存充足时不回收,不足时回收,适用于内存敏感的高速缓存;弱引用(WeakReference),GC 扫描时无论内存是否充足都会回收,生命周期短于软引用;虚引用(PhantomReference),不影响对象生命周期,仅用于跟踪对象回收过程,必须与引用队列关联使用。
废弃常量与无用类判断
- 废弃常量:运行时常量池中,若常量(如字符串 "abc")无任何对象引用,则为废弃常量,可被回收;JDK1.7 后字符串常量池移至堆,JDK1.8 后方法区实现改为元空间,运行时常量池仍在方法区。
- 无用类:需同时满足三个条件:堆中无该类任何实例、加载该类的 ClassLoader 已回收、该类的 Class 对象无任何引用(无法通过反射访问),虚拟机可选择回收无用类,而非必然回收。
垃圾收集算法
- 标记 - 清除算法:分标记(标记可达对象)和清除(回收未标记对象)两阶段,效率低且产生大量内存碎片,是基础算法。
- 复制算法:将内存分为大小相等的两块,仅使用一块,满后复制存活对象到另一块并清理原块,解决碎片问题,但可用内存减半,适合新生代(存活对象少)。
- 标记 - 整理算法:标记后将存活对象向一端移动,清理端边界外内存,无碎片但效率较低,适合老年代(存活对象多)。
- 分代收集算法:结合上述算法,新生代用复制算法,老年代用标记 - 清除或标记 - 整理算法,是当前虚拟机主流算法。
垃圾收集器(HotSpot 虚拟机)
垃圾收集器是收集算法的具体实现,需根据场景选择:
- Serial 收集器:单线程收集,新生代用复制算法,老年代用标记 - 整理算法,GC 时暂停所有用户线程(STW),简单高效,适用于 Client 模式。
- ParNew 收集器:Serial 的多线程版本,行为与 Serial 一致,可与 CMS 配合,适用于 Server 模式。
- Parallel Scavenge 收集器:多线程复制算法,关注吞吐量(用户代码运行时间 / CPU 总时间),支持自适应调节策略,JDK1.8 默认新生代收集器,搭配 Parallel Old(老年代多线程标记 - 整理算法)。
- Serial Old 收集器:Serial 的老年代版本,单线程标记 - 整理算法,作为 CMS 后备方案。
- CMS 收集器:以低停顿为目标,多线程标记 - 清除算法,分初始标记(STW)、并发标记、重新标记(STW)、并发清除四阶段,并发收集但对 CPU 敏感、产生碎片、无法处理浮动垃圾,JDK14 已移除。
- G1 收集器:面向服务器,JDK9 后默认,划分堆为多个 Region,结合标记 - 复制(局部)和标记 - 整理(整体)算法,支持并行并发、分代收集、空间整合、可预测停顿,优先回收价值高的 Region。
- ZGC 收集器:JDK11 引入,JDK15 正式可用,标记 - 复制算法优化版,暂停时间控制在毫秒级,不受堆大小影响(最大支持 16TB),JDK21 支持分代 ZGC,停顿时间可缩短至 1 毫秒内。