之前自己在看书籍了解java内存结构的时候,说到java运行时数据区,很多资料书籍就是给一堆概念,头晕乎乎,索性,自己对着代码来理解一把
仅限 JDK 8+
public class HelloWorld {
private static String message = "Hello, World!"; // 静态变量
public static void main(String[] args) {
HelloWorld h = new HelloWorld(); // 实例化对象
int result = h.add(3, 5); // 调用实例方法
String messageLocal = message; // 使用静态变量
printMessage(messageLocal); // 调用静态方法
}
public int add(int a, int b) {
int sum = a + b; // 局部变量
return sum;
}
public static void printMessage(String msg) {
System.out.println(msg); // 调用本地方法(如System.out.println)
}
}
JDK 8+ 运行时数据区划分
1. 元空间(Metaspace,替代方法区)
-
存储内容:
-
类的元数据(类名、方法字节码、字段信息等)。
-
运行时常量池(符号引用、字面量描述,如
"Hello, World!"
的符号引用)。 -
静态变量(如
message
的引用,但实际字符串值在堆中)。
-
-
示例体现:
-
HelloWorld
类的元信息(包括main
、add
、printMessage
的方法字节码)。 -
符号引用(如
HelloWorld.add
方法的名称和签名)。 -
静态变量
message
的引用(指向堆中的字符串常量池)。
-
2. 堆(Heap)
-
存储内容:
-
所有对象实例(如
new HelloWorld()
创建的对象)。 -
字符串常量池(实际字符串值,如
"Hello, World!"
的具体内容)。
-
-
示例体现:
-
new HelloWorld()
实例对象。 -
字符串
"Hello, World!"
的实际值(存储在堆的字符串常量池中)。
-
3. 虚拟机栈(JVM Stack)
-
存储内容:
-
每个线程私有的栈帧(局部变量表、操作数栈、动态链接、方法出口等)。
-
-
示例体现:
-
main
方法的栈帧:-
局部变量
args
(参数,引用类型,指向堆中的String[]
对象)。 -
h
(指向堆中的HelloWorld
对象)。 -
result
(基本类型int
的值)。 -
messageLocal
(引用类型,指向堆中的字符串常量池)。
-
-
add
方法的栈帧:-
参数
a
和b
(基本类型int
的值)。 -
局部变量
sum
(基本类型int
的值)。
-
-
4. 本地方法栈(Native Method Stack)
-
存储内容:
-
为 Native 方法(如
System.out.println
调用的底层 C/C++ 代码)服务的栈帧。
-
-
示例体现:
-
调用
System.out.println
时触发的 Native 方法栈帧。
-
5. 程序计数器(Program Counter Register)
-
存储内容:
-
当前线程执行的字节码指令地址。
-
-
示例体现:
-
主线程执行到
int result = h.add(3, 5);
时,记录该方法调用的指令位置。
-
关键区别(JDK 8+ 特性)
-
元空间替代方法区:
-
元空间使用本地内存(Native Memory),不再受
-XX:PermSize
和-XX:MaxPermSize
限制。 -
运行时常量池(符号引用)属于元空间的一部分,但 字符串的实际值在堆中。
-
-
字符串常量池的位置:
-
JDK 7+ 后,字符串常量池从方法区移至堆中,因此
"Hello, World!"
的实际值存储在堆中。
-
总结(JDK 8+)
-
元空间:类的元数据、符号引用(运行时常量池)、静态变量引用。
-
堆:对象实例、字符串常量池(实际字符串值)。
-
虚拟机栈:方法调用的局部变量和操作数。
-
本地方法栈:Native 方法的调用栈。
-
程序计数器:线程执行位置的记录。
通过这种划分,可以清晰理解代码中每个变量和对象在内存中的位置,这对分析内存泄漏、栈溢出、垃圾回收等问题非常有帮助!