类加载后需要将数据保存到内存中,运行时也需要从内存中获取数据,从下图也可以看出运行时数据区是相当重要的一部分,这篇我们会介绍下java虚拟机是如何存储数据、数据是如何和其他模块交互的。
一、方法区
1、类被加载后的信息会被存储在方法区内,当程序运行时,虚拟机会查找并使用存储在方法区中的类信息。
2、存储的信息
a、类:
I、类的全限定名
II、类的直接超类的全限定名
III、类或是接口
IV、类的访问修饰符
V、任何直接超接口的全限定名的有序列表
b、字段:
I、字段名
II、字段的类型
III、字段的修饰符
c、方法:
I、方法名
II、方法的返回类型
III、方法参数的数量和类型(按声明顺序)
IV、方法的修饰符
V、如果方法不是抽象的和本地的,还需要保存
- 方法的字节码
- 操作数栈和该方法的栈帧中的局部变量区的大小
- 异常表
d、类静态变量
e、常量池
3、方法表
为了提高访问效率,虚拟机对每个装载的非抽象类,都会生成一个方法表,把它作为的类信息的一部分保存在方法区。方法表是一个数组,它的元素是所有它的实例可能被调用的实例方法的直接引用,包括那些从超类继承过来的实例方法。运行时,可以通过方法表快速搜寻在对象中调用的实例方法的位置。
二、java堆
1、java程序运行时创建的所有类实例或数组都放在堆中。对象的回收由垃圾回收器自动回收,程序无法主动回收对象。
2、对象的数据由它所属的类和其所有超类声明的实例变量组成。它必须能通过该对象引用访问相应的类数据,因此对象中通常会有一个指向方法区中类的指针。
3、下图是堆的一种设计,吧堆分为两部分:一个句柄池,一个对象池,一个对象引用就是一个指向句柄池的本地指针。句柄池的每个条目由两部分:一个指向对象实例变量的指针,一个指向方法区类型数据的指针。
4、数组的存储如下图所示,数组类的名称由两部分组成,每一维用一个方括号“[”表示,用字符串或字符表示元素的类型。堆中的每个数组对象还必须保存数组的长度、数组数据,以及某些指向数组的类数据的引用。
三、程序计数器
对于一个运行中的java程序而言,其中的每一个线程都有他自己的pc寄存器,他是在该线程启动时创建的。pc寄存器的大小是一个字长,一次它既能够持有一个本地指针,也能够持有一个returnAddress。当线程执行某个java方法时,pc寄存器的内容总是下一条将被执行的指令的本地指针,也可以是在方法字节码中的相对于该方法的起始指令的偏移量。如果该线程正在执行一个本地方法,则此时pc寄存器的值是“undefined”。
四、java栈
1、每当启动一个新线程时,java虚拟机都会为它分配一个java栈。java栈是以帧为单位保存线程的运行状态。java栈只有两个操作:一帧为单位的压栈和出栈。某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。
2、java方法可以以两种方式完成,一种通过返回return返回的,称为正常返回;一种是通过抛出异常而异常终止的。完成后虚拟机都会将当前帧弹出java栈然后释放掉,这样上一个方法的帧就成为当前帧了。
五、帧栈
1、帧栈由三部分组成:局部变量区、操作数据栈和帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,它们是按字长计算买的。编译器在编译时就确定了这些值并放在了class文件中。而帧数据区的大小依赖于具体的实现。当虚拟机调用一个java方法时,他从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入java栈中。
2、局部变量区
局部变量区被组织为一个以字长为单位、从0开始计数的数组。包含对应方法的参数和局部变量。
3、操作数栈
操作数栈也是被组织成一个以字长为单位的数组。通过压栈和出栈来访问。虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据,执行计算,然后把结果压回操作数栈。
4、帧数据区
a、支持常量池解析:每当虚拟机要执行某个需要用到常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。
b、正常方法返回:帧数据区需要帮助虚拟机处理java方法的正常结束或异常终止。
c、可以将一些其他的数据,比如调试数据放入到帧数据区中。
六、本地方法栈
当程序调用本地方法时,需要通过本地方法接口访问虚拟机的运行时数据区,而任何本地方法接口都会使用某种本地方法栈。当虚拟机调用一个本地方法时,虚拟机会保持java栈不变,不再在线程的java栈栈中压入新的帧,虚拟机只是简单的动态连接并直接调用指定的本地方法。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于