JVM 内存管理与垃圾回收详解
1. JVM 所管理的内存区域
JVM 内存分为多个区域,每个区域有特定功能,生命周期和用途不同。
1.1 虚拟机栈 (Java Virtual Machine Stack)
- 描述:描述 Java 方法执行的内存模型,生命周期与线程一致。每个方法执行时创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 补充细节:
- 局部变量表存放基本数据类型和对象引用(非对象本身)。
- 操作数栈用于方法执行中的计算操作。
- 动态链接指向运行时常量池的方法引用。
- 方法出口记录返回地址(正常返回或异常退出)。
- 风险点:栈深度过大可能导致
StackOverflowError(如递归过深);栈空间不足可通过-Xss参数调整(默认 1 MB)。
1.2 堆 (Heap)
- 描述:JVM 管理内存中最大的区域,存放对象实例和数组。生命周期与 JVM 一致。
- 补充细节:
- 堆是垃圾回收(GC)的主要区域,分为新生代和老年代。
- 对象分配优先在新生代进行,大对象(如大数组)可能直接进入老年代。
- 堆大小可通过
-Xmx(最大堆)和-Xms(初始堆)配置,建议设为相同值以避免动态调整开销。
1.3 方法区 (Method Area)
- 描述:存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。
- 补充细节:
- 在 JDK 8 及之前,方法区由持久代(PermGen)实现;JDK 8+ 使用元空间(Metaspace)替代,避免
OutOfMemoryError: PermGen space。 - 元空间使用本地内存,可通过
-XX:MaxMetaspaceSize限制大小。 - 常量包括字符串常量池(String Table),优化字符串重用。
- 在 JDK 8 及之前,方法区由持久代(PermGen)实现;JDK 8+ 使用元空间(Metaspace)替代,避免
2. 堆区详细结构
堆 = 新生代 (Young Generation) + 老年代 (Old Generation)。
2.1 新生代 (Young Generation)
- 结构:新生代 = Eden 区 + Survivor 0 (S 0) + Survivor 1 (S 1)。
- 比例:默认分配为 8:1:1(Eden: S 0: S 1),可通过
-XX:SurvivorRatio调整。 - 对象分配:
- 多数新对象在 Eden 区产生。
- 当 Eden 满时,触发 Minor GC(Young GC),存活对象移动到 Survivor 区。
- Survivor 区采用复制算法,S 0 和 S 1 轮流作为 “ 目标 “ 区。
- 补充细节:
- GC 分代年龄:对象每经历一次 Minor GC,年龄加 1。默认年龄阈值 15(可通过
-XX:MaxTenuringThreshold修改),达到阈值后晋升老年代。 - 大对象处理:对象大小超过 Eden 区 50% 时,直接进入老年代(避免复制开销)。
- GC 分代年龄:对象每经历一次 Minor GC,年龄加 1。默认年龄阈值 15(可通过
2.2 老年代 (Old Generation)
- 描述:存放长期存活的对象(如缓存、全局变量)。
- 补充细节:
- 老年代 GC(Major GC)频率低但耗时长。
- 当新生代对象晋升或大对象直接分配时占用空间。
- 非堆区:即方法区(持久代),在 JDK 8+ 中为元空间。
3. 如何判断对象已死
对象不再被引用时视为 “ 已死 “,是 GC 回收的前提。
3.1 引用计数法 (Reference Counting)
- 原理:每个对象维护引用计数器。引用时 +1,失效时 -1;计数器为 0 时对象可回收。
- 缺点:
- 无法处理循环引用(如 A 引用 B,B 引用 A)。
- JVM 不采用此方法。
3.2 可达性分析法 (Reachability Analysis)
- 原理:以 GC Roots 为起点,遍历引用链。无引用链相连的对象不可用。
- GC Roots 对象:
- 虚拟机栈中引用的对象(如局部变量)。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI 引用的对象。
- 补充细节:
- 引用类型扩展:强引用(永不回收)、软引用(内存不足时回收)、弱引用(GC 即回收)、虚引用(仅用于跟踪回收)。
- 可达性分析是 JVM 主要方法,避免循环引用问题。
4. 垃圾回收算法
GC 算法决定如何回收内存。
4.1 标记 - 清除算法 (Mark-Sweep)
- 过程:标记所有需回收对象,然后统一清除。
- 缺点:
- 效率低(需遍历两次内存)。
- 产生内存碎片,可能导致提前 GC。
- 适用场景:老年代(配合标记 - 整理)。
4.2 复制算法 (Copying)
- 过程:内存分为两块(Eden + Survivor),GC 时存活对象复制到另一块,清空原块。
- 优点:高效无碎片。
- 缺点:空间利用率低(默认 50% 空闲)。
- 补充细节:新生代采用此算法,Survivor 区解决碎片问题。
4.3 标记 - 整理算法 (Mark-Compact)
- 过程:标记对象后,存活对象向一端移动,清除边界外内存。
- 优点:无碎片。
- 缺点:移动对象耗时。
- 适用场景:老年代。
4.4 分代收集算法 (Generational Collection)
- 原理:基于对象生命周期划分堆(新生代 + 老年代),采用不同算法:
- 新生代:复制算法(高效处理短期对象)。
- 老年代:标记 - 清除或标记 - 整理(减少碎片)。
- 补充细节:
- 为何有效:90% 对象 “ 朝生夕死 “,新生代 GC 频繁但快速;老年代 GC 少但慢。
- 优化:避免跨代引用(使用卡表 Card Table 加速)。
5. 垃圾回收器
JVM 提供多种回收器,可搭配使用。连线表示兼容性。
5.1 Serial 收集器 / Serial Old 收集器
- 参数:
-XX:+UseSerialGC(新生代)、-XX:+UseSerialOldGC(老年代)。 - 特点:单线程收集器,GC 时暂停所有工作线程(Stop-The-World, STW)。
- 算法:新生代复制算法,老年代标记 - 整理。
- 适用场景:单核 CPU 或小型应用(低延迟敏感)。
5.2 ParNew 收集器
- 参数:
-XX:+UseParNewGC。 - 特点:Serial 的多线程版本,默认线程数等于 CPU 核数。
- 算法:新生代复制算法。
- 补充细节:
- 线程数可通过
-XX:ParallelGCThreads调整(但不推荐)。 - 常与 CMS 收集器搭配。
- 线程数可通过
5.3 Parallel Scavenge 收集器 / Parallel Old 收集器
- 参数:
-XX:+UseParallelGC(新生代)、-XX:+UseParallelOldGC(老年代)。 - 特点:吞吐量优先(Throughput = 用户代码时间 / (用户代码时间 + GC 时间)),动态调整停顿时间。
- 算法:新生代复制算法,老年代标记 - 整理。
- 补充细节:
- 适用后台计算型应用(如批处理)。
- 通过
-XX:MaxGCPauseMillis设置最大停顿时间。
5.4 CMS 收集器 (Concurrent Mark Sweep)
- 参数:
-XX:+UseConcMarkSweepGC(老年代)。 - 特点:低停顿时间,基于标记 - 清除算法。
- 过程:
- 初始标记(STW):标记 GC Roots。
- 并发标记:遍历引用链(与用户线程并发)。
- 重新标记(STW):修正并发标记变动。
- 并发清除:回收内存(与用户线程并发)。
- 缺点:
- CPU 敏感(并发占用资源)。
- 内存碎片(需定期 Full GC 整理)。
- 无法处理浮动垃圾(并发时新产生垃圾)。
- 适用场景:Web 应用(响应优先)。
5.5 G1 收集器 (Garbage-First)
- 参数:
-XX:+UseG1GC。 - 特点:面向服务器,高吞吐低停顿,Region 分区模型。
- 结构:堆划分为多个 Region(大小相等),年轻代和老年代非连续。
- 补充细节:
- Humongous 区:存储大对象(> Region 50%),横跨多个 Region。
- 回收过程:并行与并发阶段,可预测停顿模型(通过
-XX:MaxGCPauseMillis)。 - 优点:整体标记 - 整理,局部复制算法;减少 Full GC。
- 适用场景:大内存多核系统(JDK 9+ 默认)。
6. JVM 参数配置与调优
6.1 常见参数示例
1 | java -Xmx4G -Xms4G -Xmn2G -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:MaxMetaspaceSize=256m -XX:MaxTenuringThreshold=20 |
- 参数解释:
-Xmx4G:最大堆内存 4GB。-Xms4G:初始堆内存 4GB(建议与-Xmx相同)。-Xmn2G:新生代大小 2GB(Eden + S0 + S1,推荐堆的 1/4–1/3)。-Xss128k:线程栈大小(默认 1MB)。-XX:NewRatio=4:老年代/新生代比例 = 4:1(即新生代占堆的 1/5)。-XX:SurvivorRatio=8:Eden/Survivor 比例 = 8:1(默认)。-XX:MaxMetaspaceSize=256m:元空间上限。-XX:MaxTenuringThreshold=20:晋升老年代年龄阈值(默认 15)。
6.2 GC 类型与触发条件
- Scavenge GC (Minor GC):
- 触发:Eden 区满时。
- 过程:回收新生代,存活对象移动到 Survivor 或老年代。
- Full GC (Major GC):
- 触发:
- 老年代满。
- 元空间满。
System.gc()调用。- 堆大小动态调整。
- Survivor 区存活对象超过 50% 时(晋升老年代)。
- 过程:回收整个堆,停顿时间长。
- 触发:
6.3 JVM 调优方向
目标:减少 Full GC 次数和 STW 时间。
- 调优策略:
- 内存配置:
- 设置
-Xmx和-Xms相同。 - 新生代大小优化(避免过小导致频繁 GC)。
- 元空间限制(防止 OOM)。
- 设置
- GC 选择:
- 低延迟:CMS 或 G1。
- 高吞吐:Parallel Scavenge。
- 监控工具:
- 使用
jstat、jvisualvm分析 GC 日志。 - 通过
-Xlog:gc*输出 GC 详情。
- 使用
- 代码优化:
- 避免大对象分配。
- 减少全局变量(降低老年代压力)。
- 使用软/弱引用管理缓存。
- 内存配置:
- 进阶调优:
- 避免
System.gc()。 - 调整晋升阈值(
-XX:MaxTenuringThreshold)。 - G1 区域大小设置(
-XX:G1HeapRegionSize)。
- 避免
7. 总结
JVM 内存管理和垃圾回收是 Java 性能核心。合理配置堆大小、选择合适的回收器(如 G1 或 CMS),并监控 GC 日志,可显著提升应用稳定性。实际调优需结合应用场景(如高并发 Web 或大数据处理),通过工具分析和迭代测试实现最优。
本博客所有文章除特别声明外,均采用 CC-BY-SA-3.0 许可协议。转载请注明来源 张小剩的博客!










