深入理解java虚拟机--->虚拟机字节码执行引擎

来源:互联网 发布:知乎 感动 句子 编辑:程序博客网 时间:2024/05/22 18:55

1 运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每一个栈帧中包括有局部变量表 , 操作数栈,动态连接 , 方法返回地址

1.1 局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在java编译的class文件中,方法的Code属性中存储了max_locals确定了该方法的局部变量表的最大值。
局部变量表的最小单位为Slot,一个Slot能存储一个(char,int,short,boolean,float,byte,reference,returnAddress)类型的数据。
虚拟机通过索引定位的方式使用局部变量表,索引值从0开始定位,如果是实例方法(非static方法),则第一个slot的值为this指针。
局部变量表的slot空间是可以重用的。如果当前PC值已经超过了变量的作用域,则可以将这个slot供其他变量使用。

public class Test1 {public static void main(String[] args) {{byte[] placeholder = new byte[64*1024*1024] ; } System.gc();}}
[GC (System.gc())  67113K->66248K(149504K), 0.0027281 secs][Full GC (System.gc())  66248K->66068K(149504K), 0.0314608 secs]
public class Test1 {public static void main(String[] args) {{byte[] placeholder = new byte[64*1024*1024] ; } int a = 0 ; System.gc();}}
[GC (System.gc())  67113K->66136K(149504K), 0.0158172 secs][Full GC (System.gc())  66136K->532K(149504K), 0.0459912 secs]

注:局部变量表中的变量不像类变量有"准备阶段",类变量在准备阶段赋系统初值,在初始化阶段赋予程序定义的初始值。而局部变量如果没有赋予初始值是不能应用的。

1.2 操作数栈

操作数栈又称操作栈,其最大深度在编译的时候就写入到Code属性的max_satcks数据项中。在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,

每个栈帧都包含一个指向运行时常量池中改栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分在类加载阶段或者第一次使用的时候就转化为直接引用,这称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。


2.3 方法返回地址

退出当前方法的两种方式为:执行引擎遇到任意一个方法返回的字节码指令在方法执行中遇到异常且这个异常没有在方法体内得到处理
退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数栈中,调整PC计数器以指向方法调用指令后面的一条指令等

3 方法调用

3.1 解析

在类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析能够成立的前提是:方法在程序真正运行之前就有一个可确定的版本,并且这个方法的调用版本在运行期间不变。符合这个前提的方法主要包括静态方法私有方法。前者与类之间关联,后者外部不能访问。还有就是实例构造器,父类方法final修饰的方法

3.2 分派

3.2.1 静态分派

public class StaticDispatch {static abstract class Human{}static  class Man extends Human{}static  class Women extends Human{}public void sayHello(Human human){System.out.println("hello human");}public void sayHello(Man man){System.out.println("hello man");}public void sayHello(Women women){System.out.println("hello women");}public static void main(String[] args) {Human man = new Man() ; Human women = new Women()  ; StaticDispatch sd = new StaticDispatch() ; sd.sayHello(man);sd.sayHello(women);}}
hello humanhello human
Human man = new Man() ; 

将上面的"Human"称为静态类型 , 后面的"Man"称为实际类型 , 静态类型在编译器可知,而实际类型变化结果需要在运行可以确定。虚拟机重载时是通过参数的静态类型而不是实际类型来作为判定依据的。而静态类型在编译器可知,因此,在编译阶段,javac编译器会根据参数的静态类型决定使用哪一个重载版本。
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派

import java.io.Serializable;public class Overload {static void sayHello(char arg){System.out.println("char");}static void sayHello(int arg){System.out.println("int");}static void sayHello(long arg){System.out.println("long");}static void sayHello(float arg){System.out.println("float");}static void sayHello(double arg){System.out.println("double");}static void sayHello(Character arg){System.out.println("Character");}static void sayHello(Serializable arg){System.out.println("Serializable");}static void sayHello(char... args){System.out.println("char ...");}public static void main(String[] args) { Overload.sayHello('c');}}
其重载顺序为char->int->long->float->double->Character->Serializable->char...

3.2.2 动态分派

public class DynamicDispatch {static abstract class Human{public void sayHello(){System.out.println("Human say hello");}}static class Man extends Human{@Overridepublic void sayHello(){System.out.println("man say hello");}}static class Woman extends Human{@Overridepublic void sayHello(){System.out.println("women say hello");}}public static void main(String[] args) {Human man = new Man() ; Human women = new Woman() ; man.sayHello();  women.sayHello();  }}

通过javap得到其class文件为:

可以看到,对于sayHello()函数的调用都是通过常量池中Human.sayHello()这个符号引用的。其最终执行的结果和目标方法的结果不同的原因是invokevirtual指令的多态查找。invokevirtual指令的运行时解析过程为:

  • 找到操作数栈的第一个元素所指向的对象的实际类型,记作C。
  • 如果在类型C中找到与常量中的描述符和简单名称相符的方法,则进行访问权限验证,通过返回这个方法的直接引用,不通过返回java.lang.IllegalAccessError
  • 否则,按照继承关系从下往上找,重复步骤2
  • 如果一直没找到,返回java.lang.AbstractMethodError

3.2.3 单分派与多分派

单分派是根据一个宗量()对目标方法进行选择,多分派是指根据多于一个宗量对于目标方法进行选择。


3.2.4 虚拟机动态分配的实现

虚拟机对于动态分配时搜索合适的目标方法不是直接搜索,常用的"稳定优化"手段就是为类在方法区建立一个虚方法表

3.3 动态类型语言支持

3.3.1 动态类型语言

动态类型语言的一个重要特征是"变量无类型而变量值有类型"

MethodHandle与Reflection的比较:

  • MethodHandle方法是模拟字节码层次的方法调用,其中MethodHandles.lookup()方法中的findStatic() , findVirtual() , findSpecial()对应于字节码中的invokeStatic , invokeVirtual&invokeInterface , invokeSpecial这几条字节码指令.
  • Reflection中的java.lang.reflect.Method中的信息比MethodHandle信息多

例子:

import java.lang.invoke.CallSite;import java.lang.invoke.ConstantCallSite;import java.lang.invoke.MethodHandle;import java.lang.invoke.MethodHandles;import java.lang.invoke.MethodType;public class InvokeDynamicTest {public static void testMethod(String s){System.out.println(s);}public static CallSite BootstrapMethod(MethodHandles.Lookup lookup , String name , MethodType mt) throws NoSuchMethodException, IllegalAccessException{return new ConstantCallSite(lookup.findStatic(InvokeDynamicTest.class, name, mt)) ; }private static MethodType MT_BootstrapMethod(){return MethodType.methodType(CallSite.class, new Class<?>[]{MethodHandles.Lookup.class , String.class , MethodType.class}) ; }private static MethodHandle MH_BootstrapMethod() throws NoSuchMethodException, IllegalAccessException{return MethodHandles.lookup().findStatic(InvokeDynamicTest.class, "BootstrapMethod", MT_BootstrapMethod()) ; }private static MethodHandle INDY_BootstrapMethod() throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, TypeNotPresentException, Throwable{System.out.println("hahaha");CallSite callSite = (CallSite) MH_BootstrapMethod().invokeWithArguments(MethodHandles.lookup() , "testMethod" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;)V", null)) ; return callSite.dynamicInvoker() ; }public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, TypeNotPresentException, Throwable {INDY_BootstrapMethod().invokeExact("123") ; }}
0 0
原创粉丝点击