Skip to content

JVM 的内存区域是如何划分的? ​

  • 标签:JVM、Java

🏛 业务架构师 ​

JVM 内存区域分两大部分(JDK 8+):

线程私有(随线程生灭)

  • 程序计数器:当前线程执行位置——唯一不会 OOM 的区域;
  • 虚拟机栈:方法调用的栈帧(局部变量、操作数)——StackOverflowError 的来源:深递归的业务逻辑(树形结构解析、级联计算);
  • 本地方法栈:native 方法用。

线程共享

  • 堆:对象实例的家,GC 主战场,新生代(Eden+Survivor)+ 老年代;
  • 方法区/元空间:类元数据(本地内存)——动态类生成业务(CGLIB 代理、Groovy 脚本、反射框架)可能撑爆 Metaspace;
  • 直接内存(堆外,Netty 的舞台)——不归 GC 管,泄漏要靠 NMT 与堆外监控发现。

业务排障地图(这是记忆的真价值):

  • 接口偶发 500 + 栈溢出 → 看栈区:递归深度没有出口的树解析、XML/JSON 深层嵌套解析被恶意构造(安全攻击常用深嵌套打崩服务——解析深度限制是防护项);
  • 堆 OOM → 大对象(一次拉百万行)、缓存无限增长、内存泄漏——业务代码的内存纪律问题;
  • Metaspace 涨 → 动态代理无上限生成类(每次请求 new 一个代理的写法);
  • 容器 OOMKilled 但堆正常 → 堆外 + 元空间 + 线程栈超了容器 limit——容器内存预算表(堆+元+栈+堆外+余量=limit)必须齐。

一句话:内存区域图是故障的分区地图——症状落在哪个区,根因就在哪类业务行为。

🏗 系统架构师 ​

按"共享 vs 私有"两分(JDK 8+):

线程共享:

  • 堆:对象实例、数组,GC 主区域;分代:新生代(Eden : Survivor0 : Survivor1 = 8:1:1,复制算法)+ 老年代(标记整理/清除);
  • 方法区(元空间,本地内存):类元信息、运行时常量池、JIT 代码缓存;JDK 8 起从永久代改为元空间,容量默认不限(受物理内存约束),解决 PermGen 易 OOM 的历史问题。

线程私有:

  • 虚拟机栈:每方法调用压入一个栈帧(局部变量表、操作数栈、动态链接、方法出口);线程请求的栈深超限 → StackOverflowError(深递归);栈容量不足扩展失败 → OOM;
  • 本地方法栈:native 方法的栈;
  • 程序计数器:当前线程字节码行号指示器,唯一无 OOM 区域;线程切换后靠它恢复执行点。

直接内存(堆外,NIO DirectByteBuffer、Netty 池化内存):不受堆 GC 直接管理,泄漏排查要用 NMT(NativeMemoryTracking)与 pmap。

排查映射(工程价值):OOM 类型与区域一一对应——堆 OOM(Java heap space:泄漏/缓存滥用)、元空间 OOM(动态代理 ClassLoader 泄漏)、栈 SOE(递归)、直接内存 OOM(Netty 未 release/堆外配置不足)、GC overhead limit(堆快满回收不动)。先按区域定位,再上工具(heap dump + MAT / NMT),这张图是 JVM 问题排查的索引页。

💻 全栈开发专家 ​

线程私有三区 + 线程共享两区:

text
┌────────────── 线程私有(随线程生灭) ──────────────┐
│ 程序计数器:当前字节码行号(唯一不 OOM 的区域)      │
│ 虚拟机栈:栈帧 = 局部变量表 + 操作数栈 + 动态链接 + 返回地址 │
│ 本地方法栈:native 方法用(HotSpot 与虚拟机栈合用)  │
└───────────────────────────────────────────┘
┌────────────── 线程共享 ────────────────────┐
│ 堆:新生代(Eden + S0 + S1)+ 老年代;对象实例的家 │
│ 方法区(JDK8+ = 元空间,本地内存):类元信息、      │
│   运行时常量池、JIT 代码缓存(字符串常量池在堆)    │
└───────────────────────────────────────────┘
(栈外补充:直接内存/NIO DirectByteBuffer,不受堆管理但受 -XX:MaxDirectMemorySize 限制)

每个区域对应一种典型故障(这么答才有工程味):

  • 栈:StackOverflowError(递归过深);线程过多 unable to create new native thread(每线程默认 1MB 栈,栈总量受 OS 限制);
  • 堆:OutOfMemoryError: Java heap space(泄漏/一次查太多行);
  • 元空间:动态代理/CGLIB 类泄漏 → OutOfMemoryError: Metaspace;
  • 直接内存:Netty 堆外没释放 → Direct buffer memory。

1.8 的关键变化:永久代被移除,类元数据到 Metaspace(本地内存、动态扩容),字符串常量池 1.7 已挪堆——这是"为什么有 Metaspace"和"内存区域划分"两道题的交汇点。

参数速记:-Xms/-Xmx 堆、-Xss 栈、-XX:MaxMetaspaceSize 元空间、-XX:SurvivorRatio=8。