JVM 内存模型与类加载机制
运行时数据区
JVM 将内存划分为以下几个区域:
┌──────────────────────────────────────┐
│ 方法区 │ ← 类信息、常量、静态变量
│ (元空间 Metaspace, Java 8+) │
├──────────────────────────────────────┤
│ 堆(Heap) │ ← 对象实例、数组
│ ┌─────────┬─────────┬─────────┐ │
│ │ 新生代 │ 老年代 │ │ │
│ │ Eden S0 S1│ │ │ │
│ └─────────┴─────────┴─────────┘ │
├──────────────────────────────────────┤
│ 虚拟机栈 │ ← 栈帧:局部变量表、操作数栈
│ 每个线程私有,每个方法对应一个栈帧 │
├──────────────────────────────────────┤
│ 本地方法栈 │ ← native 方法
├──────────────────────────────────────┤
│ 程序计数器 │ ← 当前线程执行的字节码行号
├──────────────────────────────────────┤
│ 直接内存 │ ← NIO DirectBuffer(堆外)
└──────────────────────────────────────┘堆(Heap)
所有线程共享,存储对象实例。
| 区域 | 说明 |
|---|---|
| Eden | 新对象分配区 |
| Survivor 0 / 1 | 年龄代换,默认 -XX:SurvivorRatio=8 |
| 老年代 | 长期存活的对象 |
常用参数:
-Xms512m -Xmx2g -Xmn256m 初始/最大堆、新生代大小
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m 元空间
-XX:SurvivorRatio=8 Eden:S0:S1 = 8:1:1
-XX:+UseCompressedOops 压缩对象指针(默认开启)虚拟机栈
每个线程私有,一个方法调用对应一个栈帧:
| 栈帧内容 | 说明 |
|---|---|
| 局部变量表 | 基本类型 + 对象引用 |
| 操作数栈 | 字节码指令的操作数 |
| 动态链接 | 指向运行时常量池的方法引用 |
| 返回地址 | 方法退出后恢复调用者状态 |
异常:
- 递归深度过大 → StackOverflowError
- 动态扩展无法申请到内存 → OutOfMemoryError
方法区(元空间)
JDK 8+ 以元空间替代永久代,使用本地内存
| 存储内容 | 说明 |
|---|---|
| 类型信息 | 类名、访问修饰符、父类、接口 |
| 字段信息 | 字段名、类型、修饰符 |
| 方法信息 | 方法名、参数、返回值、字节码 |
| 常量池 | 字面量 + 符号引用 |
| 静态变量 | static 字段 |
| JIT 代码缓存 | 即时编译后的机器码 |
对象创建过程
加载类 ──► 分配内存 ──► 内存初始化 ──► 设置对象头 ──► 执行 <init>1. 分配内存
两种方式:指针碰撞(连续规整)或空闲列表(不规整)
并发安全:
- CAS + 失败重试(Eden 区默认)
- TLAB(Thread Local Allocation Buffer):线程本地缓冲区
-XX:+UseTLAB 开启 TLAB
-XX:TLABSize=512k TLAB 大小2. 对象内存布局
┌───────────────────────┐
│ 对象头(Mark Word) │ ← 8 字节(32位)/ 12 字节(64位未压缩)
├───────────────────────┤
│ 类型指针(Class Pointer)│ ← 4 字节(压缩)/ 8 字节(未压缩)
├───────┬───────────────┤
│ 实例数据 │ padding... │ ← 按类型对齐
└───────┴───────────────┘Mark Word(64位 JVM):
未锁定 : unused(25) | identity_hashcode(31) | unused(1) | age(4) | biased_lock(1) | lock(2)
偏向锁 : thread(54) | epoch(2) | unused(1) | age(4) | biased_lock(1) | lock(2)
轻量锁 : ptr_to_lock_record(62) | lock(2)
重量锁 : ptr_to_monitor(62) | lock(2)
GC标记 : mark(62) | lock(2)估算对象大小:
java
// 空对象:12B 头 + 4B padding = 16B
new Object();
// 3 个 int:12B 头 + 12B 数据 = 24B
class Point { int x, y, z; }
// String(JDK8): 12B 头 + 4B hash + 4B value(ref) + 4B padding = 24B + char[]3. 对象访问
| 方式 | 描述 | 代表 |
|---|---|---|
| 直接指针 | reference 直接指向对象,访问快 | HotSpot |
| 句柄 | reference 指向句柄池,GC 时只需改句柄 | 早期 JVM |
类加载机制
类加载过程
加载 → [验证 → 准备 → 解析] → 初始化 → 使用 → 卸载
↑ 连结(Linking) ↓| 阶段 | 说明 |
|---|---|
| 加载 | 通过全限定名获取二进制字节流,转为方法区数据结构,生成 Class 对象 |
| 验证 | 文件格式、字节码语义、符号引用等验证 |
| 准备 | 为静态变量分配内存并设置零值(final 直接赋值) |
| 解析 | 将符号引用转为直接引用(常量池解析) |
| 初始化 | 执行 <clinit>,收集所有静态变量赋值 + 静态代码块 |
触发初始化(主动引用)
new、getstatic、putstatic、invokestaticreflect调用- 子类初始化 → 父类先初始化
- main 方法所在类
MethodHandle- 接口定义了
default方法 → 实现类初始化时触发
被动引用不会触发初始化:
java
// 1. 子类引用父类静态字段
System.out.println(Child.a); // 只有 Parent 初始化
// 2. 定义数组
Parent[] arr = new Parent[10]; // 不触发 Parent 初始化
// 3. 常量引用编译期进入常量池
System.out.println(Parent.HELLO); // 不触发初始化双亲委派模型
┌─────────────────────────────┐
│ Bootstrap ClassLoader │ ← C++ 实现,加载 jre/lib/rt.jar
└─────────────┬───────────────┘
│ 委托
┌─────────────▼───────────────┐
│ Extension ClassLoader │ ← jre/lib/ext/*.jar
└─────────────┬───────────────┘
│ 委托
┌─────────────▼───────────────┐
│ Application ClassLoader │ ← classpath,加载用户代码
└─────────────┬───────────────┘
│ 委托
┌─────────────▼───────────────┐
│ Custom ClassLoader │ ← 用户自定义
└─────────────────────────────┘工作流程:
类加载请求 → 检查已加载 → 向上委托 → 父加载器尝试加载
↓
加载失败 ← 加载成功返回 Class 对象优势:
- 沙箱安全:核心类库不会被用户代码替换
- 避免重复加载
破坏双亲委派
| 场景 | 解决方案 |
|---|---|
| JDBC SPI | ServiceLoader 使用线程上下文类加载器 |
| Tomcat | 优先加载 WEB-INF/classes 下的类 |
| OSGi | 网状类加载器 |
| 热部署 | 自定义 ClassLoader 隔离 |
线程上下文类加载器:
java
Thread.currentThread().setContextClassLoader(loader);
// 第三方框架通过它加载用户类Java 9 模块化
rt.jar、tools.jar取消 → 以 JMOD 模块化- 引入
ModuleLayer模块层 - 平台类加载器取代 Extension ClassLoader
常用 JVM 参数速查
堆设置
-Xms<size> 初始堆
-Xmx<size> 最大堆
-Xmn<size> 新生代
-XX:NewRatio=2 老年代:新生代 = 2:1
-XX:SurvivorRatio=8 Eden:S0:S1 = 8:1:1
-XX:MaxDirectMemorySize 直接内存上限元空间
-XX:MetaspaceSize=128m 触发 GC 的阈值
-XX:MaxMetaspaceSize=256m 最大元空间
-XX:+UseCompressedClassPointers 压缩类指针(默认开启)栈
-Xss512k 线程栈大小
-XX:ThreadStackSize=512k 同上