JVM 内存模型与垃圾回收:从 OutOfMemoryError 说起
【摘要】 JVM 内存模型与垃圾回收:从 OutOfMemoryError 说起写 Java 的人大概都见过 OutOfMemoryError,但很多人第一反应是"内存不够,加 -Xmx"。其实 OOM 只是表象,真正要解决的是"为什么不够"。这就得从 JVM 的内存结构和垃圾回收机制说起。 JVM 内存结构JVM 把运行时数据区划分为几个部分,可以简单理解为"线程私有的"和"线程共享的"两大类。线...
JVM 内存模型与垃圾回收:从 OutOfMemoryError 说起
写 Java 的人大概都见过 OutOfMemoryError,但很多人第一反应是"内存不够,加 -Xmx"。其实 OOM 只是表象,真正要解决的是"为什么不够"。这就得从 JVM 的内存结构和垃圾回收机制说起。
JVM 内存结构
JVM 把运行时数据区划分为几个部分,可以简单理解为"线程私有的"和"线程共享的"两大类。
线程私有区域(随线程创建和销毁):
- 程序计数器:记录当前线程执行到哪一行字节码,几乎没有 OOM 风险。
- 虚拟机栈:方法调用时创建栈帧,存放局部变量、操作数栈等。递归太深会
StackOverflowError。 - 本地方法栈:和虚拟机栈类似,但服务于 native 方法。
线程共享区域(所有线程共用):
- 堆(Heap):对象实例的主要存放地,绝大多数 OOM 都发生在这里。分为新生代(Eden + Survivor)和老年代。
- 元空间(Metaspace):JDK 8 之后取代了永久代,存放类元信息、常量池等。默认使用本地内存,配置不当也可能 OOM。
对象在堆中的生命周期
新对象先在 Eden 区 分配。Eden 满了触发 Minor GC(也叫 Young GC),存活的对象被移到 Survivor 区。每次 GC 后还存活的对象"年龄 +1",达到阈值(默认 15)就晋升到 老年代。老年代满了触发 Full GC,如果回收后空间仍然不足,就抛出 OutOfMemoryError。
简单理解:Minor GC 频繁但耗时短,Full GC 少但耗时长,应尽量避免频繁 Full GC。
怎么判断对象"已死"?
GC 回收的前提是判断对象不再被引用。主流 JVM 用的是 可达性分析:从一组称为 GC Roots 的根对象出发,沿着引用链向下搜索,能到达的对象就是"存活"的,否则就是"垃圾"。
常见的 GC Roots 包括:
- 虚拟机栈中引用的对象(局部变量)
- 方法区中静态属性引用的对象
- 本地方法栈中 JNI 引用的对象
- 被 synchronized 持有的对象
注意:equals() 和 hashCode() 与 GC 无关,GC 只看引用关系。
常见垃圾回收器
- Serial / ParNew:单线程 / 多线程的新生代收集器,现在用得少了。
- Parallel Scavenge:新生代收集器,注重吞吐量,适合后台计算任务。
- CMS(Concurrent Mark Sweep):老年代收集器,追求低停顿,但会产生内存碎片,JDK 9 开始被废弃。
- G1(Garbage-First):JDK 9 之后的默认回收器,把堆划分为多个 Region,可以预测停顿时间,兼顾吞吐量和低延迟。
- ZGC / Shenandoah:新一代低延迟回收器,停顿时间控制在毫秒级,适合超大堆场景。
排查 OOM 的实用步骤
- 加启动参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof,让 JVM 在 OOM 时自动导出堆转储文件。 - 用工具分析:MAT(Memory Analyzer Tool)、JProfiler、VisualVM 等打开 hprof 文件,查看哪些对象占用了最多内存。
- 定位根因:常见原因包括内存泄漏(比如静态集合只增不减)、大对象未释放、缓存无上限等。
调优小贴士
- 不要一上来就调 GC 参数,先确认是不是代码问题(泄漏、不合理缓存)。
- 新生代和老年代的比例可以通过
-XX:NewRatio调整,默认是 2(老年代是新生代的 2 倍)。 - G1 下用
-XX:MaxGCPauseMillis指定期望的最大停顿时间,让 JVM 自动调整。 - 元空间 OOM 通常是因为动态生成类太多(如 CGLIB、Groovy),可以设置
-XX:MaxMetaspaceSize限制上限。
一句话总结
- 堆是 OOM 的高发区,元空间也可能 OOM。
- 对象从新生代晋升到老年代,Minor GC 频繁,Full GC 要尽量避免。
- G1 是目前的主流选择,超大堆可以考虑 ZGC。
- 遇到 OOM 先 dump 堆再分析,别盲目加内存。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)