JVM的结构
结合《深入理解Java虚拟机:高级特性及最佳实践》、《实战Java虚拟机——JVM故障诊断与性能优化》、油管视频,下图是我对JVM结构的总结:
要理解JVM的结构,其实可以从java程序怎么运行的角度去理解:
java程序运行的是class文件,所以需要类装载子系统来把class文件加载到内存中运行,而class文件具体是加载存放到jvm中一块叫方法区的内存空间,方法区除了存放类信息外,还划分了一块叫运行时常量池的区域,用来存放字符串字面量、数字常量等信息。
class文件加载了,内存中有类信息了,java程序中就可以创建对象。那么对象又存哪里?当然是我们耳熟能详的堆内存了。另外,除了堆内存,还有一块直接内存,这块内存是堆外的内存,是直接向系统申请的内存空间,这块内存访问速度比堆内存块,比如Java的NIO库就使用了直接内存。
上面几样都是跟程序的数据存储相关的,而程序需要实现运行,以下的就是相关的:
Java栈: Java是多线程的,一条运行的线程就会有一个对应的Java栈。而一个线程跑的程序,会调用很多的函数,一个函数就对应Java栈中一个栈帧。栈帧存放着函数运行需要的东西,栈帧包含了:局部变量表、操作数栈、帧数据区等等。帧数据区是用来支持栈帧做常量池解析、方法返回、异常处理的。比如当Java字节码需要访问常量池时,帧数据区保存则常量池的指针,方便程序访问常量池;比如异常处理,帧数据区中会有一个异常处理表,方便在发生异常时找到处理异常的代码。
Exception table:
from to target type
4 16 19 any
19 21 19 any
复制代码
上面的异常处理表,表示字节码偏移量4~16字节可能抛出异常,如果抛出异常,跳转到偏移量19的地方继续执行。
程序计数器: 英文名是Program Counter Register,直译是“程序计数器寄存器”,所以国内的博客、书籍叫法有些统一。有的叫“程序计数器”,有的叫“PC寄存器”。我这里参考《深入理解Java虚拟机-JVM高级特性与最佳实践》的表述方法,叫程序计数器。程序计数器就是用来记录线程当前执行到哪一行字节码的。
本地方法栈: 结构跟Java栈差不多,区别就是它是用来执行本地方法的。
以上的方法区、Java堆、程序计数器、Java栈、本地方法栈构成了JVM的运行时数据区。
有了运行时数据区来存放Java程序运行的所有数据,就可以用执行引擎来把程序执行跑起来了。比如Interpreter是解析器,用来读取执行字节码的指令;JIT Compiler是即时编译器,可以即时把字节码指令编译机器码,弥补了Interpreter的性能不足;GC就是垃圾收集器,用来收集在运行时产生的垃圾内存。
关于局部变量表的一个细节
我在看《实战Java虚拟机-JVM故障诊断与性能优化》时看到一些有趣的地方,做了测试,顺便记录下来。
细节1:不同编译器编译出来局部变量表的大小有可能不一样。
public class TestStackDeep {
private static int count = 0;
public static void recursion(long a, long b, long c) {
long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
count++;
recursion(a, b, c);
}
public static void main(String args[]) {
try {
recursion(0L,0L,0L);
} catch (Throwable e) {
System.out.println("deep of calling = " + count);
e.printStackTrace();
}
}
}
复制代码
上面代码中的recursion方法,有3个long类型的形参,方法中还定义了10个局部变量。在Intellij IDEA中使用jclasslib的插件可以查看到这个方法的局部变量表的大小:
这里是在Intellij IDEA中使用jdk7的javac编译器编译出来的,局部变量表占26个字:
改成eclipse编译器后,局部变量表占6个字:
“字”是指计算机内存中占据的一个单独的内存单元编号的一组二进制串。一般32位计算机上一个字为4个字节长度
细节2:局部变量表的槽位
public class LocalVar {
public void localvar1() {
int a = 0;
System.out.println(a);
int b = 0;
}
public void localvar2() {
{
int a = 0;
System.out.println(a);
}
int b = 0;
}
public static void main(String[] args) {
}
}
复制代码
还是使用jclasslib来查看局部变量表。其中,
localvar1的局部变量表(最大占3个字):
localvar1的局部变量表(最大占2个字):
注意看localvar1的Index列显示占的槽位是0,1,2三个槽位,一个槽位占一个字,所以localvar1的局部变量表最大占3个字;
而注意看localvar2的Index列显示占的槽位是1,0,1,其中槽位“1”复用了,这叫叫槽位复用,所以localvar2的局部变量表最大占2个字