一个Java文件的执行全部过程你确定都清楚吗?( 三 )


局部变量表基本存储单元是 slot(变量槽),用于存储各种类型的数据,其中 long 和 double 会占用两个 slot,其他基本数据类型以及对象引用变量占用一个 slot 。

一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
这也说明了为什么类方法不能使用 this 而实例方法可以(实例方法会直接在索引为0的位置创建一个 this 参数保存,所以在实例方法中使用 this 就是直接使用这个参数的)
同时局部变量表的槽位是可以重用的,当前一个局部变量失效后,下一个变量使用空出来的位置 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
上面这个方法是实例方法,包含 this,应该有四个 index 槽位 ,但是因为 b 是在括号里作用的,出了括号就失效了,所以它的位置(index=3的位置)被新设置的 c 所占用 。
操作数栈先进后出结构,是当前方法执行的位置,在方法执行时,会根据编译生成的字节码按顺序将要操作的数据从局部变量表中进入入栈,栈中的数据只能从栈顶向下操作,不能跨数据 。比如代码 x=x+1,在执行时会将 x 先压入栈,然后将 1 压入栈,然后读取到 + 的指令,将栈顶的两个数相加,再将加的结果存入局部变量表 x 的位置 。如果调用了其他方法并获取了返回值,那么在调用方法执行完毕后,该方法的返回值会被压入栈顶,然后再进行后续的操作 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
栈顶缓存技术目前只是一种想法,还未实现 。因为使用的是栈式架构,所以指令多,又由于操作数是存储在内存中的,所以频繁地读写必然会影响执行速度,所以提出将栈顶元素全部缓存在物理 CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率 。
虚方法表
因为重写方法都是虚方法 ,这些方法在编译时期都需要网上寻找直到找到所执行对象的实际类型,然后进行权限验证 。这个寻找的过程是比较耗时的,所以每个类会在方法区创建一个虚方法表来保存这些虚方法的实际入口 。
方法返回值1、正常返回:(boolean、byte、char、short、int)ireturn ; lreturn、freturn、dreturn、areturn(String);return(无返回值) 。
2、异常返回:如果发生异常的方法没有捕获异常而是抛给上一级,那么该异常就会被返回给调用该方法的方法去处理 。
Java 堆线程共享 。是 Java 虚拟机内存最大的一块,主要用于存储创建的对象 。根据对象的寿命、大小等因素将对象存储区域划分分为新生代、老年代 。在 1.7 开始引入了字符串常量池 。因为对象的创建销毁是非常频繁的,所以对是 JVM 中的核心位置之一,也是 OOM 发生的主要位置之一 。
本地方法栈与native(本地)方法本地方法栈(也就是最上面图中的本地接口)是 JVM 与底层交互的接口,用于调用 native 方法 。作用与 Java 虚拟栈差不多,只不过是为 native 方法服务的,是由非 Java 语言编写的 。
方法区和堆一样是线程共享,用于存储已被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓存等。方法区的实现在 1.8 之前是永久代,使用的是 JVM 的内存,在1.8开始实现变成元空间,使用的是本地内存 。之所以这样改变,是因为原来的方法区很容易发生 OOM,因为方法区的类信息被回收的条件非常苛刻,必须满足以下三点:
1、该类的所有对象都被回收;2、加载该类的类加载器被回收;3、该类对应的 Class 对象没有在任何地方被引用(无法在任何地方通过反射访问该类的方法) 。
关于第三点的 Class 对象,在一个类被加载时,会在堆中创建一个用于用于访问这个类的类信息 Class 对象 。而在成为元空间后,使用的是本地内存,所以方法区发生 OOM 的情况会极大改善 。
运行时常量池
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
当 Class 文件被类加载器加载到 JVM 中时,存储的位置就是在方法区,而在 Class 文件信息中包括着 class 文件的常量池,当 JVM 开始执行时,就会将文件常量池中的数据加载到 方法区内部的运行时常量池,变成运行时状态,并将符号引用转成直接引用 。
符号引用和直接引用:当在调用中调用某个类的类方法、类属性、接口方法、接口属性时,因为在执行前,对应的类、接口都还在 Class 文件常量池中,没有加载到内存中,所以不能确定这些类、接口加载后的具体位置,这时就需要一种方式来确认位置,通常使用类的全名+属性名/方法名 来唯一标识要调用的方法/属性,这种标识就是符号引用,等到对应的类加载到内存后,再将这些唯一标识改成在内存中的位置,这种就是直接引用 。


推荐阅读