跳转到内容

运行时内存区域

用途:判断“内存去哪了”、哪块区域会 OOM 时查阅

区域 线程共享 存什么 溢出表现
堆 共享 对象实例、数组 java.lang.OutOfMemoryError: Java heap space
元空间(Metaspace) 共享 类元数据、方法字节码结构、运行时常量池 Metaspace(类加载器泄漏、动态代理生成类)
栈 每线程 栈帧:局部变量表、操作数栈、动态链接 StackOverflowError(深递归)/ unable to create new native thread
程序计数器 每线程 当前字节码行号 几乎不排查
本地方法栈 每线程 JNI 调用 少见
直接内存(堆外) 共享 NIO DirectBuffer、Netty 池、MMAP Direct buffer memory OOM,堆 dump 里看不到
  1. 字符串常量池在堆中(JDK 7 起从永久代移来),intern() 行为随之改变。
  2. 对象头包含 Mark Word(哈希、GC 年龄、锁状态)与类指针;开启压缩指针时 64 位对象头通常 12 字节,加对齐填充。小对象大量存在时,头部与对齐开销可能超过字段本身。
  3. 逃逸分析决定标量替换与栈上分配是否发生:未逃逸对象可能不真正分配在堆上,但不要把“逃逸分析”当调优开关依赖,它受 JIT 判断影响。
  4. 堆外内存不受 GC 直接管理,靠 Cleaner/显式释放;-XX:MaxDirectMemorySize 默认约等于堆大小。堆外泄漏表现为 RSS 持续高于 -Xmx。

进程内存构成(排查 RSS 时)

「进程内存构成(排查 RSS 时)」小节链接
RSS ≈ 堆 committed + 元空间 + 线程栈(线程数 × -Xss) + 代码缓存 + GC/JIT 结构 + 堆外直接内存 + glibc arena

常见“堆没满但被容器 OOMKill”的原因:线程数过多(每线程默认 1MB 栈)、堆外未限、glibc malloc arena 碎片。对策:限制线程数与 -Xss、显式设置 MaxDirectMemorySize、必要时用 malloc arena 调优或换分配器。

目的 命令/工具
堆使用与分代 jstat -gcutil <pid> 1s、JMX、Prometheus JVM exporter
对象分布 jmap -histo:live、MAT 分析 dump、Arthas heapdump + 分析
类加载数量 jstat -class、-XX:+TraceClassLoading、Arthas sc/classloader
线程与栈 jstack、Arthas thread
堆外 Native Memory Tracking(-XX:NativeMemoryTracking=summary + jcmd VM.native_memory)
误区 修正
容器里 -Xmx 等于容器内存 必须为栈、元空间、堆外留余量(经验值 25-40%)
RSS 高就是内存泄漏 先看 NMT 拆解,堆外与线程栈是常见来源
永久代说法沿用 JDK 8 起为元空间,参数与行为不同
只看堆 dump 判断全部内存 堆 dump 不含栈与堆外
  • 《深入理解 Java 虚拟机》(周志明)第 2、3 章
  • OpenJDK HotSpot GC Tuning 文档(内存区域与参数)
  • Tim Boudreau / Nitsan Wakart 关于堆外内存与 RSS 的公开文章