JVM 内存模型与垃圾回收:从 OutOfMemoryError 说起

举报
Brian-Tony 发表于 2026/11/03 09:24:52 2026/11/03
【摘要】 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 的实用步骤

  1. 加启动参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof,让 JVM 在 OOM 时自动导出堆转储文件。
  2. 用工具分析:MAT(Memory Analyzer Tool)、JProfiler、VisualVM 等打开 hprof 文件,查看哪些对象占用了最多内存。
  3. 定位根因:常见原因包括内存泄漏(比如静态集合只增不减)、大对象未释放、缓存无上限等。

调优小贴士

  • 不要一上来就调 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)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。