Java虚拟机运行时数据区域可划分为以下5个部分:虚拟机栈,本地方法栈,程序计数器,堆,方法区
虚拟机栈
虚拟机栈是线程私有的数据区,他的生命周期与线程相同。虚拟机栈中划分了若干个栈帧,每个栈帧对应了一个方法,用于存储局部变量表、操作数栈,动态链接、方法出口等信息。
1)局部变量表:局部变量表是一组变量值的存储空间,用于存储线程中方法内部定义的局部变量及方法参数,局部变量表以变量槽为最小单位,对于每一个变量槽,应该都能存放一个boolean,byte,char,short,int,float,reference或returnAddress类型的数据,对于64位的数据类型,Java虚拟机会以高位对其的方式为其分配两个连续的变量槽。Java虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的变量槽数量。当一个方法被调用时,Java虚拟机会使用局部变量表来完成参数值到参数列表的传递过程,如果该方法为实例方法的话(即无static修饰符修饰的方法),则局部变量表的0号索引位置会存储this引用,需要注意的是,局部变量表的变量槽是可重用的,因为方法中局部变量的作用域不一定包括整个方法体,所以对于那些不在其作用域的变量,可以复用其变量槽,例如:
public static void main(String[] args)() {
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
int a = 0;
System.gc();
}
2)操作数栈:操作数栈是一个栈数据结构,满足先进后出原则,其中的每一个元素可以是包括long和double在内的任意Java类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2.Javac编译器的数据流分析工作保证了在方法执行的任何时候,操作数栈的深度都不会超过Code属性中max_stacks数据项设定的最大值。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种 字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。举个例子,例如整数加法的字节码指令iadd,这条指令在运行的时候要 求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会把这两个int 值出栈并相加,然后将相加的结果重新入栈。需要注意的是,虽然在概念模型中,两个不同的栈帧是作为不同方法的虚拟机栈元素,应该是完全独立的,但是在大多数虚拟机的实现中,都会对其进行一些优化,允许两个栈帧出现一部分重叠,使其节约一些空间,如下图所示:
3)动态连接:每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。首先,我们的Class文件的常量池中存在着大量的符号引用(符号引用存在原因我理解是在Java编译器将*.java编译成.class文件时,由于我们此时不知道被引用的类,字段,方法的内存地址,所以我们只能暂时生成符号引用,待到运行时再通过符号引用索引其内存地址),这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化称之为静态解析。另外一部分会在每一次运行期间都转化为直接引用,这部分称之为动态连接。这两个转化的具体过程可见方法调用部分。
4)方法返回地址:当一个方法执行时,只有两种方法可以退出,一种是正常退出(即正常调用完成),另一种是方法执行过程中遇到了异常退出(即异常调用完成)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的。无论采用何种退出方式,在方法退出之后,都必须返回最初方法被调用的位置,方法正常退出时,主调方法的PC计数器的值就可以作为返回地址,方法异常退出时,返回地址是通过异常处理器表确定的
本地方法栈
本地方法栈与虚拟机栈十分相似,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
Java堆
Java堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程所共享的一块内存区域,在虚拟机启动时创建。此内存的唯一目的就是存放对象实例,Java世界‘几乎’所有对象实例都会在这里分配内存(判定为不会线程逃逸的对象实例也可在栈上分配)
Java堆是垃圾收集器管理的内存区域,因此一些资料中他也被称为‘GC堆’。
所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区,以提升对象分配时的效率,需要注意的是,无论是哪个区域,Java堆中存储的都只能是对象的实例,将Java堆细分的目的只是为了更好的回收内存,更快的分配内存
《Java虚拟机规范》规定,Java堆可以处于物理上的不连续内存空间中,但在逻辑上它应该被视为连续的
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的
方法区
方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
方法区在JDK8以前,习惯被称之为永久代,但实际上方法区不能等同于永久代,永久代只能说是方法区的一种实现方法。
《Java虚拟机规范》对方法区的约束非常宽松,它内存也和Java堆一样可以物理上不连续,可选择固定大小或可扩展方式,甚至还可以选择不实现垃圾收集,不过实际情况中,对于这一部分的垃圾收集还是相当有必要的。这区域的内存回收主要是针对常量池的回收和对类型的写在。
程序计数器
程序计数器是一块比较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,为了线程切换后能回复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响
附网上找的两张图