一、运行时数据区域
一个基本的JVM运行时内存模型如下所示
二、程序计数器
1.概念
2.程序计数器的特点
(1)线程隔离性,每个线程工作时都有属于自己的独立计数器。
(2)执行java方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址
(3)执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。(4)程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计。
(5)程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。
三、java虚拟机栈
1.概念
2.栈帧栈帧组成
(1)局部变量表
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。并且在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)「String是引用类型」,对象引用(reference类型) 和 returnAddress类型(它指向了一条字节码指令的地址),总而言之就是基本数据结构,和局部变量的引用存在栈中的局部变量表中
局部变量表的容量以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间,例如boolean、int、float等。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。
为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的, 也就是说当PC计数器的指令指已经超出了某个变量的作用域(执行完毕), 那这个变量对应的Slot就可以交给其他变量使用。可以节省栈帧空间。 但是会影响到系统的垃圾收集行为如大方法占用较多的Slot,执行完该方法的作用域后没有对Slot赋值或者清空设置null值,垃圾回收器便不能及时的回收该内存。
(2)操作数栈
用于储存字节码指令
(3)动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接。这里简单提一下动态连接的概念,后面在详细讲解.
(4)方法出口
当一个方法开始执行后,只有2种方式可以退出这个方法 :
方法返回指令 : 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。
无论采用任何退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息。
一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。
而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
3.可能发生的异常:
四、本地方法栈
五、Java堆
1.概念
2.堆内存的分配
(1)回收内存的角度
3.异常
六、方法区
1.概念
2.运行时常量池
3.存放的内容
(1)类型信息
类的完整名称(比如,java.long.String)
类的直接父类的完整名称
类的直接实现接口的有序列表(因为一个类直接实现的接口可能不止一个,因此放到一个有序表中)
类的修饰符
(2)类型的常量池 (即运行时常量池)
每一个Class文件中,都维护着一个常量池(这个保存在类文件里面,不要与方法区的运行时常量池搞混),里面存放着编译时期生成的各种字面值和符号引用;这个常量池的内容,在类加载的时候,被复制到方法区的运行时常量池 ;
字面值:就是像string, 基本数据类型,以及它们的包装类的值,以及final修饰的变量,简单说就是在编译期间,就可以确定下来的值;
符号引用:不同于我们常说的引用,它们是对类型,域和方法的引用,类似于面向过程语言使用的前期绑定,对方法调用产生的引用;
存在这里面的数据,类似于保存在数组中,外部根据索引来获得它们
(3)字段信息
- 声明的顺序
- 修饰符
- 类型
- 名字
(4)方法信息
- 声明的顺序
- 修饰符
- 返回值类型
- 名字
- 参数列表(有序保存)
- 异常表(方法抛出的异常)
- 方法字节码(native、abstract方法除外,)
- 操作数栈和局部变量表大小
(5)类变量(即static变量)
非final类变量
在java虚拟机使用一个类之前,它必须在方法区中为每个非final类变量分配空间。非final类变量存储在定义它的类中;
final类变量(不存储在这里)
由于final的不可改变性,因此,final类变量的值在编译期间,就被确定了,因此被保存在类的常量池里面,然后在加载类的时候,复制进方法区的运行时常量池里面 ;final类变量存储在运行时常量池里面,每一个使用它的类保存着一个对其的引用;
(6)对类加载器的引用
jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。
(7)对Class类的引用
jvm为每个加载的类都创建一个java.lang.Class的实例(存储在堆上)。而jvm必须以某种方式把Class的这个实例和存储在方法区中的类型数据(类的元数据)联系起来, 因此,类的元数据里面保存了一个Class对象的引用;
(8)方法表
为了提高访问效率,必须仔细的设计存储在方法区中的数据信息结构,也就是方法的代码逻辑
七、直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现