JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分

java
import com.test.domain.User;
public class Main{
static int count = 0;
public static void main(String[] args){
run();
}
public static void run(){
String name = "zhangsan";
User user = new User();
}
public native void test();
}
JVM 的内存结构主要分为以下几个部分:
虚拟机栈
用来存储每个线程的执行信息,即每个线程都有自己独立的虚拟机栈。每个方法在执行时都会在虚拟机栈中创建一个对应的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
比如上面的代码,在运行
main()方法的时候,会先把main()方法压入 JVM 虚拟机栈;当main调用run()方法时,又会新建一个栈帧并将run()压入栈中。该栈帧的局部变量表中会存储name和user这两个局部变量。
堆
是 JVM 中最大的一块内存区域,被所有线程共享,在虚拟机启动时创建,用于存放对象实例。
仍以上面代码为例:
new User()会在堆内存中创建一个User对象,并返回其地址;run()方法栈帧中的局部变量user则保存该对象的引用(即地址)。
从 JDK 7 开始,字符串常量池(String Table)被从方法区移入了堆内存 。因此,字面量"zhangsan"会被存放在堆中的字符串常量池里。
原因包括:
- 方法区(永久代)空间有限,大量字符串容易导致
OutOfMemoryError: PermGen space;- 字符串对象本身就在堆中,将常量池也放在堆中更便于垃圾回收。
元空间(方法区)
在 JDK 8 之前称为"方法区",JDK 8 及以后由"元空间"实现,使用本地内存(而非 JVM 堆内存),用于存储已被虚拟机加载的类元数据,包括:类结构信息、字段和方法的描述信息(如名称、类型、访问标志)、方法字节码、运行时常量池(非字符串对象)等。
仍以上述代码为例:
public class Main的类信息、static int count = 0;的字段描述 (如字段名"count"、类型int、修饰符static)等都会被加载到元空间中。
注意 :静态变量的值 (如count = 0)并不直接存在元空间,而是作为Main.class对象的一部分,存储在 Java 堆 中。
🔍 拆解说明:static int count = 0; 到底存哪?
| 内容 | 存储位置 | 说明 |
|---|---|---|
字段名 "count"、类型 int、访问标志 static |
✅ 元空间 | 属于类的元数据,用于反射、JIT 编译等 |
静态变量的值 0 |
✅ Java 堆 | 作为 Main.class(Class 对象)的一个字段值存储 |
Main.class 这个对象本身 |
✅ Java 堆 | 在 JDK 8+ 中,所有 Class 对象都在堆中 |
因此,当你访问
Main.count时,JVM 实际是从堆中的Main.class对象里读取这个值。
程序计数器
用于记录当前线程正在执行的 Java 方法的 JVM 字节码指令地址。每个线程都有独立的程序计数器。当线程切换时,程序计数器能确保线程恢复执行时从正确的位置继续。
如果当前执行的是 Native 方法,程序计数器的值为 undefined。
本地方法栈
与 Java 虚拟机栈作用类似,但专为虚拟机调用 Native 方法(如 C/C++ 编写的代码)服务。在 HotSpot 虚拟机中,本地方法栈和 Java 虚拟机栈实际是合二为一 的。本地方法执行时也会创建栈帧,同样可能出现 StackOverflowError 或 OutOfMemoryError。
上面代码中的
public native void test();方法,其调用过程由本地方法栈支持。
✅ 总结 :
JVM 运行时内存主要分为五部分:虚拟机栈、堆、元空间(方法区)、程序计数器、本地方法栈。其中堆和元空间是线程共享的,其余均为线程私有。
作者:不会写程序的未来程序员
首发于 CSDN
版权声明:本文为原创文章,转载请注明出处。