跳到主要内容

JavaMemoryModel

Java 内存管理手册

—— 从运行时结构到垃圾回收深度解析


Java 内存管理

区域存储内容生命周期线程共享性
方法区(Method Area)类信息、常量、静态变量JVM 启动到关闭共享
堆(Heap)对象实例、数组对象创建到垃圾回收共享
虚拟机栈(VM Stack)方法调用的栈帧(局部变量、操作数栈)方法调用开始到结束线程私有
本地方法栈(Native Stack)JNI(本地方法)调用本地方法执行期间线程私有
程序计数器(PC Register)当前线程执行的字节码行号线程创建到销毁线程私有

1. Java 内存模型(JMM)概述

Java 内存管理由 JVM(Java Virtual Machine) 自动处理,开发者无需手动分配释放内存,但仍需理解其机制以避免内存泄漏或性能问题。
核心目标:
✅ 高效分配对象内存
✅ 自动回收无用对象(GC)
✅ 隔离线程安全(栈私有 vs 堆共享)


2. 运行时数据区(Runtime Data Areas)

JVM 内存划分为以下核心区域:

(1) 方法区(Method Area)

  • 存储内容:
    • 类元信息(类名、父类、接口、方法代码等)。
    • 运行时常量池(如 String 常量、final 静态变量)。
  • 关键点:
    • JDK 8 后由 元空间(Metaspace) 实现(取代永久代),使用本地内存,默认无上限(需监控防溢出)。
    • 溢出错误:OutOfMemoryError Metaspace

(2) 堆(Heap)

  • 存储内容:所有对象实例和数组。
  • 分代设计(优化 GC 效率):
    • 新生代(Young Generation):新创建的对象。
      • Eden 区:对象首次分配处。
      • Survivor 区(S0S1):Minor GC 后存活的对象。
    • 老年代(Old Generation):长期存活的对象(经过多次 GC 后晋升)。
    • 永久代(PermGen,JDK 7 及之前):已废弃,被元空间取代。
  • 溢出错误:OutOfMemoryError Java heap space

(3) 虚拟机栈(VM Stack)

  • 存储内容:每个线程私有的栈帧(Stack Frame),包含:
    • 局部变量表(基本类型、对象引用)。
    • 操作数栈(方法计算时的临时数据)。
    • 动态链接(指向方法区的方法引用)。
    • 方法返回地址。
  • 栈深度问题:
    • 默认栈大小 1MB(可通过 -Xss 调整)。
    • 溢出错误:StackOverflowError(如无限递归)。

(4) 本地方法栈(Native Method Stack)

  • 为 JNI(本地方法,如 CC++ 代码)服务的栈空间。
  • 溢出错误:同虚拟机栈。

(5) 程序计数器(PC Register)

  • 记录当前线程执行的字节码行号(线程私有)。
  • 唯一无溢出风险的区域。

3. 垃圾回收(Garbage Collection, GC)

(1) 对象存活判定

  • 引用计数法(已淘汰):循环引用无法处理。
  • 可达性分析(主流):
    • GC Roots 包括:
      • 虚拟机栈中的局部变量。
      • 方法区中的静态变量。
      • JNI 引用的本地对象。
    • 从 GC Roots 出发,不可达的对象标记为垃圾。

(2) 垃圾回收算法

算法 原理 适用场景 优缺点

标记-清除 标记垃圾 → 直接清除 老年代 简单,但产生内存碎片
复制算法 将存活对象复制到另一块内存 新生代(Eden→S0S1) 无碎片,但浪费 50% 空间
标记-整理 标记垃圾 → 存活对象向一端移动 → 清除 老年代 无碎片,但移动对象耗时
分代收集 结合上述算法,按对象生命周期分代处理 所有区域 综合性能最优(HotSpot 默认)

(3) 垃圾收集器(HotSpot 实现)

收集器 分代 算法 特点

Serial 新生代 复制 单线程,STW(Stop-The-World)
Parallel Scavenge 新生代 复制 多线程,吞吐量优先
CMS 老年代 标记-清除 并发收集,低延迟(JDK 14 移除)
G1 全堆 分Region+标记-整理 平衡吞吐与延迟(JDK 9+ 默认)
ZGC 全堆 染色指针 超低延迟(JDK 15+ 生产可用)

(4) GC 调优参数

# 堆大小设置
-Xms512m -Xmx1024m # 初始堆 512MB,最大堆 1GB
-XXNewRatio=2 # 新生代老年代 = 12
-XXSurvivorRatio=8 # EdenS0S1 = 811

# 选择收集器
-XX+UseG1GC # 启用 G1
-XX+UseConcMarkSweepGC # 启用 CMS(已废弃)

4. 内存问题诊断与工具

(1) 常见内存问题

  • 内存泄漏:对象无用时仍被引用(如静态集合未清理)。
  • 内存溢出:对象过多或堆设置过小(OutOfMemoryError)。
  • GC 过频:新生代太小导致频繁 Minor GC。

(2) 诊断工具

工具 用途 命令用法

jstat 监控 GC 统计信息 jstat -gcutil pid 1000
jmap 生成堆转储快照(Heap Dump) jmap -dumpformat=b,file=heap.hprof pid
VisualVM 图形化分析内存和线程 集成于 JDK,支持插件扩展
MAT(Eclipse) 分析 Heap Dump 查找泄漏对象 加载 .hprof 文件
Arthas 在线诊断 JVM 问题(如监控方法调用) watch com.example.MyClass


5. 实战:内存优化案例

案例 1:缓存泄漏

问题:静态 HashMap 缓存数据未清理,导致老年代堆积。
解决:改用弱引用 WeakHashMap 或定期清理。

案例 2:频繁 Full GC

现象:System.gc() 被滥用或元空间未限制。
优化:禁用显式 GC(-XX+DisableExplicitGC)并限制元空间(-XXMaxMetaspaceSize=256m)。

案例 3:线程栈溢出

场景:递归调用未设终止条件。
修复:改为循环或增加栈大小(-Xss2m)。


6. 总结

主题 关键点

内存结构 堆存对象、栈存方法调用,方法区存类信息,PC 寄存器记录执行位置。
GC 机制 分代收集(新生代复制算法,老年代标记-整理),G1ZGC 为现代低延迟选择。
问题排查 结合 jstat、Heap Dump、MAT 定位泄漏或溢出。
调优原则 避免过早优化,监控后再调整堆大小、分代比例和收集器。

附录:

通过本手册,您可系统掌握 Java 内存管理的核心知识,高效应对生产环境问题! 🚀