
类加载器:把class文件从 磁盘/网络 拉进来放到内存,准备好
运行时数据区:内存的仓库,存放代码、变量等
执行引擎:把Java字节码转化成机器可执行的代码
本地方法接口:负责调用外部代码(eg.C++等)
运行时数据区

虚拟机栈
栈帧:每调用一个Java方法就压入一个栈帧;方法正常返回或者异常退出,栈帧都弹出
内存:固定大小,也可以动态扩展(eg-Xss:设置虚拟机栈大小(比如 -Xss256k))
👉 栈不是存对象的!对象在堆;栈里存的是对象引用。

局部变量表
1.存:方法参数、方法内定义的局部变量
2.是一组Slot(变量槽) 一个slot 32 bit


3.局部变量表在编译期就确定了长度,运行期间大小不变
4.GC可达性:
局部变量表里面的引用,就算代码走到后面不用了,只要引用还在 slot 里,GC 就不会回收对象
可以手动 a = null 辅助回收,或者用 javac -g 调试信息、JIT 优化擦除引用
操作数栈

1.是临时数据栈,基于栈式指令集,用来做计算、传参
2.也是编译期确定最大深度
3.对比:寄存器架构(x86/ARM)是用寄存器;
JVM 是栈式架构,指令简单、可移植,缺点指令多。
4.图 1 中的压 1 压 2 指的是压入【当前栈帧的操作数栈】
动态链接
Java 编译成 class 字节码的时候,不知道方法 / 字段在内存里的真实地址
只能写符号引用
动态链接就是在方法运行时,把栈帧里保存的符号引用,翻译成直接引用(内存地址)的过程
方法返回地址
方法执行完之后,回到调用者方法的位置,方法返回地址记录的就是这个位置
正常退出:执行 return 系列指令,返回值压到调用方操作数栈
异常退出:没有返回值,异常要往上抛,靠异常表处理
注意:异常不属于返回地址,栈帧里有独立的异常处理逻辑。
方法调用流程举例

本地方法栈
本地方法:用 native 关键字修饰的 Java 方法
方法体不是 Java 写的,是 C/C++ 等原生语言写的,放在外部动态库(.so/.dll/.dylib)里
这个方法只有声明,没有 {} 方法体
本地方法栈:专门给 JNI 本地方法(native 方法)服务的栈,和 Java 虚拟机栈是一对
1.存储:
java
本地方法的栈帧(C/C++ 的栈帧,不是 Java 栈帧):
本地方法的局部变量
方法返回地址
函数调用上下文
JNI 调用相关信息
//⚠️ 注意:不是存 Java 对象,是原生代码的调用栈。
2.可能抛出的异常:
StackOverflowError:线程调用本地方法时,栈深度超过允许的最大栈深度OutOfMemoryError:本地方法栈动态扩展,扩展时内存不够
很多虚拟机(HotSpot)里,本地方法栈和虚拟机栈共用一块内存,所以这两个栈的 OOM / StackOverflow 经常放一起讨论。
3.HotSpot
一句话:JVM 是接口,HotSpot 是这个接口最主流的实现类。
JVM 是一套规范(Specification),只定义运行时数据区、字节码、指令集等抽象规则
HotSpot 是 Oracle 官方默认的 JVM 实现(就是我们平时用的 JDK8、JDK17 默认的虚拟机)
我们日常说的 JVM 调优参数 -Xss、-Xmx、-Xms、-XX:+UseG1GC 全部都是 HotSpot 的参数
HotSpot 没有单独给本地方法栈做独立的内存区域:
- 虚拟机栈 + 本地方法栈,合二为一
-Xss参数同时控制这两个栈的大小- 所以你调
-Xss,既改 Java 方法栈大小,也影响本地方法栈
堆
堆是JVM 中最大的一块内存区域
唯一目的:存放对象实例 + 数组
是 GC 垃圾回收的主战场
线程共享,启动时分配
可通过 -Xms(初始堆)、-Xmx(最大堆)控制大小
整体结构
java
Heap
├─ Young 新生代 //存放刚创建、生命周期短的对象,GC 频率很高,这里的 GC 叫 Minor GC(YGC)
│ ├─ Eden 伊甸区
│ ├─ S0 From Survivor
│ └─ S1 To Survivor
└─ Old 老年代 //存放长期存活、大对象:Major GC / Full GC,开销远大于 YGC,速度慢很多。
Full GC 之后还是放不下 → OOM(`java.lang.OutOfMemoryError: Java heap space`)
//比例默认:Eden : S0 : S1 = 8 : 1 : 1(可以用 -XX:SurvivorRatio修改)
//新生代占整个堆默认 1/3,老年代 2/3(-XX:NewRatio 调整)
JDK8 移除了永久代,改用元空间(Metaspace,直接在本地内存,不在堆里)
永久代(PermGen)= JDK7 及之前放类信息的地方,在堆里面
元空间(Metaspace)= JDK8 之后替代永久代,不在堆里,用操作系统本地内存
具体下面方法区部分有涉及
新生代分配规则
对象优先分配在Eden
=>Eden满了=>YGC=>存活的对象复制到 To Survivor
=>...=>再次触发YGC=>Eden + From Survivor里存活的对象复制到To
=> 互换角色(S0 <=> S1来回切换)
1.✅ 一句话理解互换: From 是存存活对象的那个,To 是空的目的地;每次 YGC 完,两者身份交换。
2.对象在 Survivor 熬过一定次数 GC(默认 15 次,
-XX:MaxTenuringThreshold),晋升到老年代
方法区
注意:方法区是《Java 虚拟机规范》里定义的逻辑区域,不是物理内存;
不同虚拟机实现不一样,最常用的 HotSpot 有两个大版本变化:
- JDK1.7 及以前:永久代(PermGen),属于堆的一部分
- JDK1.8+:元空间(Metaspace),直接使用本地内存(Native Memory),不再在堆里
存什么
1.类型信息
- 类名、父类、接口列表、访问修饰符
- 字段信息(字段名、类型、修饰符)
- 方法信息(方法名、返回值、参数、修饰符、方法字节码)
- 常量池(运行时常量池,重点!)
2.运行时常量池
- 编译期生成的字面量(字符串、数字)、符号引用,class 文件里的常量池加载后放到这里
- 支持动态:
String.intern()、动态生成类、反射动态生成常量
3.静态变量
JDK1.7 之前静态变量在永久代;JDK1.7 静态变量移到堆,1.8 继续在堆
4.静态常量
5.方法表
6.注解信息