运行时数据区域

image-20230331143717622

1、程序计数器

该部分可以看作当前线程所执行的字节码的行号指示器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

由于Java虚拟机的线程是通过轮流切换线程,然后分配处理器时间来实现的。任何时间,一个处理器(多核处理器就是一个内核)都只会执行一个线程中的一条指令。所以说切换线程后,之前执行到哪里都需要进行保存,所以每一个线程都会有一个独立的程序计数器。

2、Java虚拟机栈

Java虚拟机栈也是也是线程私有的,它的生命周期与线程相同。

该部分描述的是Java方法执行的线程内存模型:每个方法执行时,虚拟机都会创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口等信息。方法被调用到结束,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表

该部分存放了编译器可知的Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

这里的对象引用,可以理解为在局部变量表中存储了指向堆中具体对象的指针。

3、本地方法栈

本地方法栈与虚拟机栈的作用是很相似的,区别是虚拟机栈是为虚拟机执行Java方法提供服务,而本地方法栈是为虚拟机使用本地(Native)方法服务。这个本地方法指的是使用Java代码去调用非Java代码的接口,比如说用Java调用C或者C++的接口,这些方法就是本地方法。

4、Java堆

Java的堆是被所有线程所共享的内存区域,在虚拟机启动时创建,此区域的唯一目的是为了存放对象实例,几乎所有的对象实例都是在这里分配内存。

该部分是由垃圾回收器管理的,从回收内存的角度看,由于垃圾收集器大多都是采用分代收集,所以Java堆中会出现新生代,老年代,永久代,Eden空间等。从分配内存的角度看,所有线程共享的Java堆中,可以划分出多个线程私有的分配缓冲区,以提升对象分配效率。

Java堆可以处于物理上不连续的内存空间中,但是在逻辑上他应该被视为连续的。但是对于大对象,最典型的数组,可能会要求连续的内存空间。

5、方法区

方法区也是各个线程共享的内存区域,用于存储已经被虚拟机加载了的类型信息,常量,静态变量,即时编译器编译后的代码缓存等。该部分的垃圾回收发生的相对较少,主要是针对常量池的回收和对类型的卸载。

每个类在编译后都会有一个Class对象,这个Class对象会放在一个.class文件当中,这个就是对类的描述。这个Class对象的作用就是用于运行时提供或者获得某个对象的信息。

比如一个Students类,他编译后会生成一个用于描述Students类的Class对象,该对象记录了Students的属性以及方法。这些内容是存储在方法区当中。

运行时常量池

该部分是方法区的一部分。Class文件中会存储常量池表,用于存放编译器生成的各种字面量符号引用。常量池表在类加载后会放到方法区的运行时常量池中。

这里的字面量和符号引用的意思:

1
2
int a = 12;
String str = "hello world";

这里的12,以及hello world 就是所谓的字面量。而a以及str会被存储在本线程私有的栈中。在类加载的时候,像int a以及String str会被解析成指向常量池具体常量的直接引用

参考

《深入理解Java虚拟机》