JVM内存模型
以Math类为例,体会下图JVM内存模型
java
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute(int a, int b) {
return a + b;
}
}
JVM内存模型如下:

- 堆和方法区都是线程共享的(上图JVM中淡橙色方块表示);
- 栈、程序计数器、本地方法栈都是线程私有的(上图JVM中紫色方块表示);
- 字节码执行引擎负责:执行字节码指令、修改程序计数器用于上下文切换找到指定代码行、执行gc等工作;
- 元空间是方法区的一种实现,它将方法区中的元数据存储在直接内存中,而不是在堆内存中;
- 堆和方法区都会导致full gc,也就是说直接内存不够时也会full gc;
- 动态链接:将方法调用的符号转为内存地址,从而可以找到对应的对象或其他方法;
- 方法出口:记录方法结束回到上一个栈帧后从哪里继续执行;
- 本地方法栈:用于执行本地方法(如C/C++编写的方法),栈帧与Java栈帧不同,栈帧中包含了本地变量表、操作数栈、动态链接、方法出口等信息
- 如上图局部变量表中的math的值是分配在堆上的math对象的地址值,所以说栈会有很多指针指向堆中的对象。
- 在Math类中可以看到user静态变量,因此会分配到方法区,但是new user()对象还是会分配到堆中,类似这种情况也会使得方法区有很多指针指向堆。
在minor gc过程中对象挪动后,引用如何修改?
1. 对象复制机制
对象在堆内部挪动的过程其实是复制,原有区域对象还在,一般不直接清理。JVM内部清理过程只是将对象分配指针移动到区域的头位置即可:
- 扫描S0区域,扫到GC Root引用的非垃圾对象时,将这些对象复制到S1或老年代
- 扫描完成后,将S0区域的对象分配指针移动到区域的起始位置
- S0区域之前的对象并不直接清理,当有新对象分配时,原有区域里的对象才会被覆盖
2. 引用更新机制
Minor GC在根扫描过程中会记录所有被扫描到的对象引用(在年轻代这些引用很少,因为大部分都是垃圾对象不会扫描到),如果引用的对象被复制到新地址了,最后会一并更新引用指向新地址。
具体的引用更新过程:
第一阶段:标记和复制
css
1. 从GC Root开始遍历所有可达对象
2. 将存活对象从Eden区和From Survivor区复制到To Survivor区或老年代
3. 在复制过程中,JVM会维护一个转发表(Forwarding Table)
第二阶段:引用更新
markdown
1. 遍历所有已知的引用位置(栈、方法区、其他堆对象)
2. 检查这些引用是否指向已移动的对象
3. 如果指向已移动对象,则根据转发表更新引用地址
3. 转发表(Forwarding Table)机制
转发表是JVM用来跟踪对象移动的关键数据结构:
- 记录映射关系:原地址 → 新地址
- 快速查找:通过哈希表或类似结构实现O(1)查找
- 临时存在:只在GC过程中存在,GC完成后释放
4. 引用更新的范围
需要更新引用的位置包括:
- 栈中的引用:局部变量表中的对象引用
- 方法区中的引用:静态变量、常量池中的引用
- 堆中的引用:其他对象的字段引用
- JNI引用:本地方法中的对象引用
5. 性能优化策略
记忆集(Remembered Set):
- 记录跨代引用关系,避免全堆扫描
- 只需要更新记忆集中记录的引用位置
- 大大减少了引用更新的工作量
并发更新:
- 在某些GC算法中(如G1),引用更新可以并发进行
- 减少STW(Stop The World)时间
6. 示例说明
java
// GC前的状态
Object obj1 = new Object(); // 地址:0x1000
Object obj2 = new Object(); // 地址:0x2000
obj1.field = obj2; // obj1引用obj2
// Minor GC过程中
// 1. obj1复制到新地址:0x3000
// 2. obj2复制到新地址:0x4000
// 3. 更新引用:obj1.field = 0x4000
// GC后的状态
// obj1现在在地址0x3000
// obj2现在在地址0x4000
// obj1.field正确指向0x4000
这种机制确保了在对象移动后,所有引用都能正确指向新的内存位置,保证了程序的正确性。
关于元空间(Metaspace)和方法区(Method Area)的关系
1. 方法区的概念
- 方法区是JVM规范中定义的一个逻辑区域,用于存储类的元数据信息
- 它是线程共享的内存区域,存储了每个类的结构信息,如:
- 类的字段和方法数据
- 方法的字节码
- 常量池信息
- 类和方法的访问修饰符
2. 元空间的本质
- 元空间是HotSpot JVM对方法区的具体实现方式
- 在JDK 8及以后版本中,元空间取代了之前的永久代(PermGen)
- 它将类的元数据存储在直接内存(Native Memory)中,而非Java堆内存
3. 直接内存 vs 堆内存的区别
堆内存存储特点:
- 受JVM管理,有明确的大小限制(-Xmx参数控制)
- 参与垃圾回收过程
- 内存分配和释放由JVM控制
直接内存存储特点:
- 属于操作系统内存,不受JVM堆大小限制
- 默认情况下只受操作系统可用内存限制
- 可以动态扩展,避免了永久代的内存溢出问题
4. 这种设计的优势
- 避免OOM问题:不再受限于固定的永久代大小
- 更好的内存管理:元数据的生命周期与类加载器绑定,更容易管理
- 性能提升:减少了堆内存的压力,GC过程更加高效
因此,这句话准确地描述了元空间作为方法区实现的本质特征:它改变了元数据的存储位置,从堆内存迁移到直接内存,从而解决了传统永久代的一些限制。