jvm字节码
jvm字节码一般结构
使用javap -verbose 命令分析一个类,得到的是一个类字节码的魔数,版本号,常量池,类信息,类的构造方法,类的方法信息,类变量与成员变量等信息。当然也可以配合jclasslib这个idea插件来学习字节码内容
- 魔数:.class字节码的前四个字节即为魔数。魔数为固定值:0xCAFEBABE
- 版本号:魔数之后的四个字节即为版本号,前两个字节为 minor version 次版本号,后两个字节为 major version 主版本号。
- 常量池:主版本号之后的即为常量池入口,常量池是可变的,不确定的。可以将常量池看做类文件的资源仓库。常量池中主要存储两类常量:字面常量和符号引用。字面常量如文本字符串。符号引用:类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。
常量池由常量池数量和常量池表构成。- 常量池数量
常量池数量在主版本号之后,占据两个字节。 - 常量池表
在常量池数量之后。常量池数组中不同元素的类型结构是不同的,因此长度不同。但是每一种元素的第一个数据都是一个u1类型,该位置是标志位,占用一个字节,jvm在解析常量池时,会根据u1类型来获取元素的具体类型。
- 常量池数量
- 类信息:
- 类的访问修饰符(u2)
- 类的名字(常量池索引)
- 父类名字(常量池索引)
- 接口个数
- 接口表
- 成员变量:
- 成员变量数(u2)
- 成员变量表(包含类变量和和实例变量)
- 权限描述符
- 名字索引
- 类型描述符索引
- 属性数
- 属性信息
- 方法
- 方法个数(u2)
- 方法表
- 权限描述符(u2)
- 名字索引(u2)
- 类型描述符索引(u2)
- 属性数(u2)
- 属性信息
属性信息结构- 属性类型索引(u2)
- 属性信息长度(u4),本方法属性之后的信息长度
- 最大栈深(u2)
- 最大局部变量数(u2),包含方法的参数和this。
- 操作码表长度(u4)
- 操作码表(u2,java虚拟机指令集)
- 异常表长度
- 异常表
- 作用的开始行
- 作用的结束行
- 异常出现跳转行
- 异常名字索引
- 属性表长度(u2)
- 属性表:属性表是code的属性,如linenumbertype,存储源代码和字节码行号对应关系。localvariabletable,存储方法中变量的类型,名字和描述。
- 属性名索引(u2)
- 属性长度(u4)
- 属性数(u2):表示有几个属性
- linenumbertype属性内容结构:*
{
对应关系:u4类型,有多个,(u2-u2)表示字节码中行号和源代码行号的对应关系
} - localvariabletable属性内容结构:*
{
局部变量在字节码中开始作用的位置:u2类型
局部变量在字节码中作用的长度:u2类型
局部变量名称索引:u2类型
局部变量描述符索引:u2类型
局部变量localvariabletable中的索引
}
- 属性
- 属性数量(u2)
- 属性表
- 属性名索引(u2)
- 属性值
属性值长度(u4)
属性值索引(u2)
jvm class文件是线性的,但可以尝试使用json理解字节码的结构,以下为json结构。
开头*表示线性排序表示
属性带” x”表示可以有多个
{
"*Magic Number":"0xCAFEBABE",
"Version":{
"*minor version":"u2",
"*major version":"u2"
},
"Constant Pool":{
"*Constant_count":"u2",
"*Constant_info":[
"类型众多,参照表格,基本结构为u1+n个字节。"
]
},
"Class info":{
"*Access Flags":"u2,类的访问修饰符",
"*This Class Name":"u2类名",
"*Super Class Name":"u2 父类名字",
"*Interfaces_count":"接口数",
"*Interfaces_name":["接口名有多个"]
},
"Fields":{
"*Fields_count":"u2,成员变量数",
"Fields_info *x":[
{
"*access_flags":"u2,权限修饰符",
"*name_index":"u2,变量名称索引" ,
"*descriptor_index":"u2,类型描述符索引",
"*attribute_count":"u2,属性数目",
"attribute_info *x":[
{}
]
}
]
},
"Methods":{
"*methods_count":"u2,方法数",
"methods_info *x":[
{
"*access_flags":"u2,访问修饰符",
"*name_index":"u2,方法名称索引",
"*descriptor_index":"u2,类型描述符索引",
"*attribute_count":"u2,属性数目",
"attribute_info *x":[
{
"*attribute_name_index":"u2,属性名索引",
"*attribute_length":"u4,属性长度,指之后数据长度",
"*max_stack":"u2,最大栈深",
"*max_locals":"u2,最大局部变量数",
"*code_length":"u4,操作码长度",
"code":[
"*code1",
"*code2",
"*...code为u1组成"
],
"*exception_table_length":"u2,异常表长度",
"exception_table":[
{
"*Start_Pc":"u4,异常开始作用行",
"*Eed_Pc":"u4,异常结束作用行",
"*Handler_Pc":"u4,异常发生跳转行",
"*Exception_name_index":"u2,异常名索引"
}
],
"*attribute_count":"u2,属性数目",
"attribute_info *x":[
{
"*attribute_name_index":"u2,属性名索引",
"*attribute_length":"u4,属性长度,指之后数据长度",
"*attribute_count":"表示有几个对应关系",
"attribute_info":[
{
"*linenumbertype":"字节码和源码行号对照表,u4类型,有多个,(u2-u2)表示字节码中行号和源代码行号的对应关系"
},
{
"localvariabletable":"局部变量表",
"*局部变量在字节码中开始作用的位置":"u2类型",
"*局部变量在字节码中作用的长度":"u2类型",
"*局部变量名称索引":"u2类型",
"*局部变量描述符索引":"u2类型",
"*局部变量localvariabletable中的索引":"u2"
}
]
}
]
}
]
}
]
},
"Attributes":{
"*attribute_count":"u2,属性数目",
"attribute_table":[
{
"*attribute_name_index":"u2,属性名索引",
"*attribute_length":"属性值长度",
"*attribute_index":"属性值索引"
}
]
}
}
synchronized 关键字
synchronized方法 在方法上加标识符
synchronized静态方法 在方法上加标识符
synchronized代码块 编译出不同两个指令集,在中间的表示为同步的代码块。
静态变量和一般变量
静态变量和静态代码块的指令集都在clinit()方法中执行
实例变量的值和代码块都在init() 方法中执行
字节码中的异常
jdk统一采用异常表的方式处理异常
当异常处理存在finally语句块时,现代化的jvm采取的方式是将finally语句块的字节码拼接到每一个catch语句块后面,有多超catch语句块,就拼接多少次。
方法后throws的异常会在method_info下生成一个异常数组
栈帧和操作数栈,直接引用和间接引用
栈帧是一种帮助虚拟机执行方法调用与方法执行的数据结构。
栈帧本身是一种数据结构,封装了方法的局部变量表,动态链接信息,方法的返回值和操作数栈等信息。
有些符号引用在类加载阶段或第一次使用时就转换为直接引用,另一些符号引用则是在每次运行期转为直接引用。这种转换叫做动态链接,在java中体现为多态性。
栈帧结构
{
局部变量表
操作数栈
动态链接
返回地址
帧数据区
}重写和重载的不同
分派:确定执行哪个方法的过程
静态类型:引用类型,不会被改变、在编译器可知。
动态类型:实例对象类型,会变化、在运行期才可知
静态分派:调用重载的方法。调用同一个类的不同的方法,编译后会指向不同的符号引用(方法的参数不同,确定不同方法),这是显而易见的,从代码就可以看出方法调用的不同。
动态分派:调用重写的方法,若有两个父类引用指向不同的两个子类对象,编译后指向相同的符号引用,具体执行哪个实例方法是由jvm决定的。
invokevirtual:只要是调用public方法,编译后都是invokevirtual指令操作,
- invokevirtual指令执行的第一步 = 确定接受者的实际类型
- invokevirtual指令执行的第二步 = 将常量池中类方法符号引用解析到不同的直接引用上
调用重载的方法也是invokevirtual指令,这是因为重载的方法也有可能会被重写,所有的public方法都采用invokevirtual指令,也是为了更好的体现多态,多态在jvm层面是由invokevirtual指令实现的
动态绑定具体的调用过程为:
jvm是基于栈的指令集,基于栈的指令集主要的操作有入栈和出栈两种
基于栈的指令集可以在不同平台之间移植,基于寄存器的指令集是与硬件相关的。
基于栈的指令集的缺点在于完成相同操作,指令的数量多运行速度慢。基于寄存器的指令集速度则要快得多。
动态代理
在java基础中了解到了动态代理这种模式,在jvm层面,动态代理的意思是代理类的字节码是在运行期由jvm生成。
生成的代理类里面,最终还是会调用自己写的InvocationHandler实现类里面的invoke方法,