
一句话JVM 的内存按用途分为 5 块区域程序计数器、Java 虚拟机栈、本地方法栈线程私有、堆、元空间线程共享。每一块都有各自的用途和异常。5 大区域总览JVM 运行时数据区 │ ├── 线程私有每个线程独立一份 │ ├── ① 程序计数器 ← 很小记录当前线程执行到哪一行 │ ├── ② Java 虚拟机栈 ← 方法调用、局部变量、对象引用 │ └── ③ 本地方法栈 ← native 方法用的栈 │ └── 线程共享所有线程共用 ├── ④ 堆Heap ← 所有对象实例new 出来的都在这里 └── ⑤ 元空间Metaspace← 类结构信息、方法字节码、常量池线程私有区域① 程序计数器作用记录当前线程执行到哪一行字节码指令。publicintadd(inta,intb){intcab;// ← 当前执行到这里程序计数器就记录这一行returnc;}为什么需要多线程切换时切回来要知道从哪继续执行。特点JVM 规范里唯一不会 OOM 的区域。很小。② Java 虚拟机栈作用每个方法调用时分配一个栈帧方法执行完栈帧出栈。methodA 进入 栈[methodA 栈帧] methodA 调用 methodB 栈[methodA 栈帧] [methodB 栈帧] ← methodB 在栈顶 methodB 结束 栈[methodA 栈帧] ← methodB 出栈 methodA 结束 栈[] ← methodA 出栈栈帧里有什么一个栈帧 ├── 局部变量表 ← 方法里的局部变量和对象引用 ├── 操作数栈 ← 计算时的临时草稿纸 ├── 动态链接 ← 指向运行时常量池的引用 └── 返回地址 ← 方法结束后回到哪里关键理解对象在堆里栈里只存对象的引用地址“遥控器”。UserusernewUser();// 栈里 [user — 地址 0x1234] ← 遥控器// 堆里 [User 对象] ← 房子异常递归太深 →StackOverflowError。③ 本地方法栈和虚拟机栈功能一样但给native 方法使用的。// native 方法 —— 用 C/C 实现的 Java 方法publicnativeinthashCode();// Object 类底层 C 实现publicstaticnativevoidsleep(longmillis);// Thread 类Java 方法调用用虚拟机栈native 方法调用用本地方法栈——两者分开管理互不干扰。线程共享区域④ 堆Heap所有对象实例都放在堆里。newUser();// → 堆newArrayList();// → 堆newint[10];// → 堆堆内部按对象的存活时间分为两代堆 ├── 新生代朝生夕死的对象 │ ├── Eden80% ← 所有新对象先放这里 │ └── Survivor │ ├── S010% ← From │ └── S110% ← To │ └── 老年代熬过多次 GC 的对象堆是最容易出问题的区域——OOM 绝大多数就是堆满了。具体分代和对象流转见下一篇。⑤ 元空间Metaspace作用存放类的结构信息——可以理解为对象的图纸。堆里 [User 对象1] [User 对象2] ... [User 对象10000] ← 10000 份实例 元空间[User 类的结构信息] ← 只有 1 份图纸元空间里存的是类的全限定名com.example.User类的字段描述id: Long,name: String类的方法字节码常量池JDK7 vs JDK8 的变化JDK7方法区/永久代JDK8元空间位置在堆里不在堆里用本地内存大小限制有上限容易永久代 OOM默认不限制除非设置 MaxMetaspaceSize字符串常量池在方法区移到了堆里避免 OOM静态变量在方法区移到了堆里面试问答一个对象在哪个区域代码JDK8 存在哪new User()对象实例堆User.classClass 对象堆类的字段/方法/字节码“图纸”元空间hello字符串常量堆JDK8 字符串常量池在堆里static int count静态变量堆JDK8 移到了堆里栈和堆的区别栈虚拟机栈存方法调用和局部变量控制 堆存对象实例数据 栈里的引用指向堆里的对象参考来源《深入理解 Java 虚拟机》第 2 章JDK8 JVM 规范