本文参考圣思园张龙深入理解jvm
目录
Java字节码结构
Access_Flag访问标志
Fileds 字段表
Methods 方法表:
方法的属性结构
Code结构
其他结构
附加属性表
字节码补充注意事项
栈帧
字节码解释执行
Class字节码中有两种数据类型:
访问标志信息包括含该Class文件是类还是接口,是否被定义成Public,是否是abstract。如果是类,是否被声明陈final。通过上面的源代码,我们只带该文件是类并且是Public.
0x0021 是 0x0020 0x0001的并集,表示acc_public 和 acc_super
用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
第一个表示该变量为 private
方法的属性结构,方法中的每个属性都是一个attribute_info结构。
Jvm预订了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
不同的attribute通过attribute_name_index来区分。
Code attribute的作用是保存该方法的结构,如对应的字节码:
Attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
Max_stack 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
Max_loacals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。
Code_length表示该方法所包含的字节码的字节数以及具体的指令码
具体字节码即是该方法被调用时,虚拟机执行的字节码
Exception_table,这里存放的是处理异常的信息
每个Exception_table都有start_pc.end_pc,handler_pc,catch_type组成
附加属性:
LineNumberTable 这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。
Valuevirtable 表示的是变量的有效域
字节码的最后一部分,该项存放了在该文件中类或接口所定义属性的基本信息。
参考:https://segmentfault.com/a/1190000020345321?utm_source=tag-newest#item-1-1
Java类中的变量的声明最后会放入到构造方法中。
对于java类中的每一个实例方法(非static方法),其在编译或所生产的字节码当中,方法参数的数量总是会比源代码中方法的参数多一个this,它唯一方法的第一个参数位置处,这样我就可以在java的实例方法中使用this来访问当前对象的属性以及其他方法。
这个操作是在编译期间完成的,是由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问,接下来在运行期间,由jvm再掉用哪个实例的方法时,自动向实例方法传入该this参数。所以在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量。
Java字节码对与异常的处理方式:
如果我们在方法后throw 一个exception,则会在二进制文件中与code出现一个并列的exceptions
Stack frame https://www.cnblogs.com/jhxxb/p/11001238.html
栈帧是一种用于帮助jvm执行方法调用与方法执行的数据结构。
栈帧是一种数据结构,封账了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。 每个栈帧都是有一个线程来执行
符号引用,直接引用
有些符号引用会在类加载阶段或者是第一次使用就会转换为直接引用,这种转化叫做静态解析;另一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接,这体现为java的多态性。
静态解析的4中情形:1.静态方法 2.父类方法 3.构造方法 4.私有方法(公有方法会被重写和复写,所以不可以)
以上四类方法称作非虚方法,他们是在类加载阶段就可以将符号引用转换为直接引用的。
上面程序涉及到 方法的静态分派
g1静态类型是grandpa,而实际类型(真正的指向类型)是father。
结论:变量的静态类型是不会发生变化的,而实际类型则是可以发生变化的。多态的一种体现。实际类型是在运行期才可以确定的。
上面代码中,test有多个方法,是方法重载 ,是一种静态的行为。Jvm判断的依据就是根据方法接受参数的静态类型来判断调用哪一种方法。编译期就可以完全确定。
重载本身是一种静态的概念,重写是一种动态的概念。
但是我们会看到apple.test()对应的invokevirtual是fruit.test方法
方法的动态分派
方法的动态分派涉及到一个重要的概念:方法接受者
Invokevitrual字节码指令的多态查找流程
Invokevitrual找到操作数栈顶的第一个元素所指向对象的实际类型,也就是apple变量找到了Apple这个类,
如果在这个类型中,寻找到与常量池中的描述符(Fruit中test方法 )与描述符相同的方法,并且具有访问权限,如果通过,就返回这个目标方法的直接引用。
如果找到不,从继承关系,从子类到父类寻找。
Invokevitrual在运行期确定方法接受者的实际类型是什么。所以虽然apple,orange变量Invokevitrual相同的符号引用,但被解析到不同的直接引用上了。
针对于方法调用动态分派的过程,jvm会在类的方法区建立一个虚方法标的数据结构,(virtual method table, vtable),类似于上述invokeinterface,jvm会建立一个接口方法表的数据结构(interface method table, itable)。Vtable,如子类如果继承的方法与父类相同,那么子类不会直接复制一份,子类在方法表中直接指向父类方法的入口地址
现代jvm在执行java代码的时候,通常都会将解释执行与编译执行二者结合起来进行。所谓解释执行就是通过解释器来读取字节码,遇到相应的指令就去执行该指令。编译执行就是通过即时编译器(just in time,JIT)将字节码转换为本地机器码来执行。现代jvm会根据代码特点来生成相应的本地机器码。
基于栈的指令集与基于计寄存器的指令集之间的关系:
动态代理设计模式https://www.jianshu.com/p/fc285d669bc5
***深入研究