堆内存逻辑统一但物理不连续,由多个离散region/page组成;java栈为虚拟内存上限而非实际占用;metaspace使用本地内存且依赖classloader生命周期;oom需结合nmt、pmap等综合排查。

堆内存不是一块连续物理内存
很多人以为 Heap 对应操作系统里一块连续的虚拟内存区域,其实 JVM 启动后,Heap 通常由多个不连续的内存块组成(尤其是使用 G1 或 ZGC 时)。CMS 和 Parallel GC 早期版本倾向申请大块连续内存,但现代 GC 算法为兼顾并发与低延迟,会把堆切分为多个 Region(G1)或 Page(ZGC),它们在虚拟地址空间中可能离散分布。
关键点在于:JVM 只保证逻辑上统一管理,不承诺物理连续。这意味着:
-
malloc级别的系统调用可能被多次触发,每次申请的地址段互不相邻 -
top或pmap查看进程内存映射时,会看到多个标记为[anon]的、大小接近-Xmx的片段,而非单一大块 - 启用
-XX:+UseLargePages可能提升 TLB 命中率,但不会改变“多段分配”本质,只是每段内部用大页对齐
Java 栈和本地栈共享线程私有虚拟内存空间
每个 Java 线程创建时,JVM 为其分配两块栈:一个是 Java 方法调用用的 Java Virtual Machine Stack,另一个是 JNI 调用或 JVM 内部操作用的 Native Stack(也叫 C Stack)。它们共用同一段线程私有的虚拟内存区域,由操作系统按需映射(lazy allocation),初始只提交少量页(如 8KB~64KB),真正发生方法调用或本地调用时才触发放大。
常见误判是认为 -Xss 指定的就是已占用内存 —— 实际上它只是**虚拟内存上限**。你用 ps -eo pid,rss,vsz,comm | grep java 会发现 RSS 远小于 VSZ,因为大部分栈空间尚未映射物理页。
-
-Xss512k表示该线程最多可使用 512KB 虚拟地址空间,不是常驻内存 - 栈溢出错误
java.lang.StackOverflowError发生在虚拟空间耗尽(写入未映射页触发 SIGSEGV),而非物理内存不足 - 线程数暴涨时,VSZ 会线性增长,但 RSS 不一定同步涨;若 RSS 异常飙升,更可能是栈上分配了大量局部对象(逃逸分析失效)或 JNI 层泄漏
元空间不再在永久代,但依然依赖本地内存与类加载器生命周期
Metaspace 自 JDK 8 起取代了永久代(PermGen),但它并非“完全脱离 JVM 堆”,而是直接使用本地内存(native memory),由 ClassLoader 实例持有其元数据块。这意味着:
- 元空间大小受
-XX:MaxMetaspaceSize限制,但不受-Xmx影响 —— 它算进进程总 RSS,却不计入 GC 统计的堆内存 - 类卸载(class unloading)只有在对应
ClassLoader被 GC 回收后才会触发元空间释放,所以频繁热部署、OSGi 或自定义类加载器场景下,容易出现java.lang.OutOfMemoryError: Metaspace -
jstat -gc <pid></pid>中的MU(Metaspace Used)值滞后于真实占用,因释放动作异步且依赖 Full GC;而Native Memory Tracking (NMT)的[class]分类才反映实时元空间内存消耗
物理布局差异直接影响 OOM 排查路径
当看到 OutOfMemoryError,不能只看错误文字就断定是哪块内存出问题。比如:
-
java.lang.OutOfMemoryError: Java heap space→ 检查jstat -gc的OU(Old Used)和 GC 日志是否频繁 Full GC,再确认是否真缺堆内存,还是因元空间膨胀挤占了系统内存导致 mmap 失败 -
java.lang.OutOfMemoryError: Metaspace→ 先用jcmd <pid> VM.native_memory summary</pid>看[class]占比,再结合jmap -clstats <pid></pid>查活跃类加载器数量 - 无明确 OOM 报错但进程被 OS 杀死(dmesg 显示
Out of memory: Kill process)→ 很可能是 native memory 总量超标(含 DirectByteBuffer、JIT code cache、Metaspace、线程栈等),此时cat /proc/<pid>/status | grep Vm</pid>比堆参数更有说服力
真正难定位的,往往是多块内存协同失衡:比如大量线程 + 大 -Xss + 高频类加载,会让虚拟内存(VSZ)迅速逼近 128TB(x86_64 用户空间上限),而 RSS 还没爆,这时连 new Thread() 都会失败,报 java.lang.OutOfMemoryError: unable to create new native thread —— 错误信息完全没提内存,根源却在地址空间耗尽。










