JVM Class详解之一
来自: https://yq.aliyun.com/articles/7241
首先看Class中包含哪些信息简单的说所有java文件中有的信息class文件都有,编译器帮我们将java文件转化成了JVM能看懂的class格式而已
Class 概述
Class文件是一组以8位字节为基础的二进制流,各个数据项目按照严格顺序紧凑排列在Class文件中。所有的16位,32位,64位长度的数据将被构造成2个,4个,8个字节单位来标示。
ClassFile结构
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count-1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
class格式说明
- magic:魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE
- minor_version、major_version: 分别为Class文件的副版本和主版本
- constant_pool_count: 常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1
- constant_pool[]: 常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1
- access_flags: 访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性
- this_class: 类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值
- super_class: 父类索引
- interfaces_count: 接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量
- interfaces[]: 接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count
- fields_count: 字段计数器
- fields[]: 字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项
- methods_count: 方法计数器
- methods[]: 方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项
- attributes_count: 属性计数器
- attributes[]: 属性表,attributes表的每个项的值必须是attribute_info结构
废话不多说HelloWorld搞起
public class HelloWorld { String str = ""; public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
编译成class文件以后,只用javap -verbose HelloWorld.class 指令可以查看当前class的内容
同时使用UE打开class文件
我们来一起看下ClassFile结构
前4个字节为魔数,也就是0xCAFEBABE,这里都是十六进制
魔数后2个字节为副版本号,这里副版本号是0
主版本号0x0033,转为十进制,主版本号是51 标示当前class是通过jdk 1.7编译的,0x32是jdk1.6 0x31是jdk1.5
这两个字节是常量池计数器,常量池的数量为0x001A,转为十进制是26,也就是说常量池索引为1~26
从常量池开始
常量池计数器后面紧跟着就是常量池的内容所有的常量池项都具有如下通用格式:
cp_info { u1 tag; u1 info[]; } CONSTANT_Class_info { u1 tag; u2 name_index; } CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; } CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; }
后面的0x07对应tag找到是CONSTANT_Class,标示接下来的是一个class的信息。后面的 00 02 是class的name_index 标示指向常量池的第二个常量。我们再看第二个常量
第二个常量是01开头,我们查看常量类型表中对应是Utf-8,再按照utf-8的结构,后面的00 0A代表了这个utf-8的长度这里长度转换为10进制是11,后面紧跟着utf-8的实际内容
再后面0x 07,是常量池的下一个产量,也是一个class信息,后面跟00 04,name_index执行常量池的
第4个常量。
第4个常量又是utf-8,后面长度为 0x10 十进制为16,接下来的为实际内容
接下来都可以按照此方法分析。
直观结果可以通过javap指令查看
常量池后面紧跟的2个字节是Access Flag,这个表示用于标示类或接口层次的访问信息,如这个Class是类还是接口,是否为public类型,是否定义为abstrace类型。
标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口和抽象类,此标志为真,其它类为假
ACC_SYNTHETIC 0x1000 标识别这个类并非由用户代码产生
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举
我们这里0021标示为public Class
接下来的是类索引,父类索引与接口索引集合this_class,super_class,interfaces_count,interfaces
类索引
为2个字节
这里为00 01,指向常量池中第一个常量,之前我们分析过常量池中第一个常量为Class类型,内容指向第二个常量UTF-8的HelloWorld。
标示当前名为HelloWorld
父类索引
也是2个字节
指向常量中第三个常量,对应内容为java/lang/Object
接口数量
表示接口数量为0
字段表集合
00 01 标示字段数量为1字段表的格式如下
类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_coun
accessFlags为 00 00 当时当前字段无修饰符字段修饰符格式如下
标志名称 标志值 含义 ACC_PUBLIC 0x0001 字段是否为public ACC_PRIVATE 0x0002 字段是否为private ACC_PROTECTED 0x0004 字段是否为protected ACC_STATIC 0x0008 字段是否为static ACC_FINAL 0x0010 字段是否为final ACC_VOLATILE 0x0040 字段是否为volatile ACC_TRANSIENT 0x0080 字段是否为transient ACC_SYNTHETIC 0x1000 字段是否为编译器自动产生 ACC_ENUM 0x4000 字段是否为enum
name_index为 00 05指向常量池中的第五个常量
第5个常量为str,变量名为str
descriptor_index指向常量池第6个变量,为Ljava/lang/String类型
attributes_count(属性计数器,占2字节,0x0000,所以该字段没有额外需要描述的信息)
方法集合
method_count: 00 03 有3个方法
methods:方法表集合
类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_coun
access flags的定义见下表
标志名称 标志值 含义 ACC_PUBLIC 0x0001 字段是否为public ACC_PRIVATE 0x0002 字段是否为private ACC_PROTECTED 0x0004 字段是否为protected ACC_STATIC 0x0008 字段是否为static ACC_FINAL 0x0010 字段是否为final ACC_SYNCHRONIZED 0x0020 字段是否为synchronized ACC_BRIDGE 0x0040 方法是否是由编译器产生的桥接方法 ACC_VARARGS 0x0080 方法是否接受不定参数 ACC_NATIVE 0x0100 字段是否为native ACC_ABSTRACT 0x0400 字段是否为abstract ACC_STRICTFP 0x0800 字段是否为strictfp ACC_SYNTHETIC 0x1000 字段是否为编译器自动产生
这里方法access flags 为 00 01 说明方法为public的
name_index为00 07,方法名指向常量中第7个常量方法名为, descriptor_index为常量池第8个常量()V
attributes_count 为 00 01标示这个方法的属性表集合中有一个属性。属性名称为接下来2位0x0009,指向常量池中第9个常量:Code
接下来4位为 00 00 00 3D标示Code属性值的字节长度为3D,接下来为00 02标示该方法的操作数栈的深度最大值为2.
00 01标示该方法的局部变量占用空间为1.
接下来4位00 00 00 0B 为机器编译生成字节码指令的长度为11,后面11个字节就是字节码指令(字节码指令可查询虚拟机字节码指令表),这里字节码指令长度用4个字节标示,所有字节码指令超长Class编译会失败的。
再接下来为 00 00标示Code属性异常表结合为空。
再后面为 00 02,,说明Code带有2个属性, 00 10即为Code属性第一个属性的属性名成指向常量池中第16个常量
接下来的00 00 00 0E 标示LinueNumberTable属性值所占字节长度为15.接下来2位 00 03标示该line number table中有3个line number table表,start pc为 00 00 line number第 00 01个为00 04 第 00 02个为 00 0A
再后面的 00 01又是第二个方法的access flags,接着开始第二个方法。
声明:云栖社区站内文章,未经作者本人允许或特别声明,严禁转载,但欢迎分享。
</code></div>