JVM-13-Class文件结构
Class文件结构
概述
为什么要了解Class文件
字节码文件使java语言具备了跨平台性
- java虚拟机不和包括java在内的任何语言绑定,只与“Class文件”这种特定格式的二进制文件所关联,无论使用何种语言,只要能将源文件编译为正确的Class文件,那么这种语言就可以在java虚拟机上运行,可以说,同一而强大的Class文件机构就是java虚拟机的基石、桥梁
通过字节码指令可以看到一些细节,这些细节是通过代码看不到的
比如:Integer,String的使用细节
package com.ty.jvm;public class IntegerTest {public static void main(String[] args) {Integer x=5;int y=5;System.out.println(x==y);//trueInteger i1=10;Integer i2=10;System.out.println(i1==i2);//trueInteger i3=128;Integer i4=128;System.out.println(i3==i4);//false//与Integer的valueOf方法有关,但是这里体现不出来,可以通过字节码看到} }
0 iconst_51 invokestatic #2 <java/lang/Integer.valueOf>//装箱4 astore_15 iconst_56 istore_27 getstatic #3 <java/lang/System.out> 10 aload_1 11 invokevirtual #4 <java/lang/Integer.intValue>//拆箱 14 iload_2 15 if_icmpne 22 (+7) 18 iconst_1 19 goto 23 (+4) 22 iconst_0 23 invokevirtual #5 <java/io/PrintStream.println>26 bipush 10 28 invokestatic #2 <java/lang/Integer.valueOf> 31 astore_3//astore_3最多到三,三以上不加横线astore 4 32 bipush 10 34 invokestatic #2 <java/lang/Integer.valueOf> 37 astore 4 39 getstatic #3 <java/lang/System.out> 42 aload_3 43 aload 4 45 if_acmpne 52 (+7) 48 iconst_1 49 goto 53 (+4) 52 iconst_0 53 invokevirtual #5 <java/io/PrintStream.println>56 sipush 128 59 invokestatic #2 <java/lang/Integer.valueOf> 62 astore 5 64 sipush 128 67 invokestatic #2 <java/lang/Integer.valueOf> 70 astore 6 72 getstatic #3 <java/lang/System.out> 75 aload 5 77 aload 6 79 if_acmpne 86 (+7) 82 iconst_1 83 goto 87 (+4) 86 iconst_0 87 invokevirtual #5 <java/io/PrintStream.println>
前端编译器(不属于JVM)
- 想要一个java程序正确的运行在jvm中,java源码就必须要被编译为符合jvm规范的字节码
- 前端编译器的主要任务就是复制将符号java语法规范的java代码转换为符合jvm规范的字节码文件
- javac就是一种能够将java源码编译为字节码的前端编译器
- javac编译器再将java源码编译为一个有效的字节码文件过程中经历了四个步骤:词法解析、语法解析、语义解析、生成字节码
- 补充
- 前端编译器的主要任务就是复制将符号java语法规范的java代码转换为符合jvm规范的字节码文件
- Hotspot并没有强制要求前端编译器只能使用javac,只要编译结果符合jvm规范并可以被jvm所识别即可
- 除了javac之外,内置在eclipse的ECJ编译器也是前端编译器,与javac不同的是,ECJ是增量式编译器,javac是全量式编译器
- 在eclipse中,使用ctrl+s快捷键进行保存的时候,ECJ编译器所采取的编译方案是把未编译部分的源码逐行进行编译,而非每次都全量编译,因此ECJ会比javac更迅速和高效,编译质量和javac相比大致还是一样的
- ECJ不仅是eclipse的默认前端编译器,Tomcat中也是使用ECJ来编译jsp文件,由于ECJ是采用GPLv2协议开源的,所以可以在eclipse官网下载源码进行二次开发
- 默认情况下IDEA使用javac编译器,可以自己设置为AspectJ编译器(ajc)
- 前端编译器不会直接涉及编译优化等方面,而是交给JIT编译器负责
虚拟机的基石:Class文件
字节码文件里是什么
- 字节码是一种二进制的类文件,内容是jvm指令,而不像c/c++一样经由编译器直接生成机器指令
什么是字节码指令
java虚拟机指令是由一个字节长度的、代表着某种特定操作还以的操作码(opcode)以及其后跟随的零至多个代表此操作所需参数的操作数(operand)所构成,虚拟机中许多指令并不包含操作数,只有一个操作码
26 bipush 10 28 invokestatic #2 <java/lang/Integer.valueOf> 31 astore_3 32 bipush 10
如何解读供虚拟机解释执行的二进制字节码
一个一个二进制看,使用Notepad++,安装HEX-Editor插件,或者使用Binary Viewer
使用javap指令(JDK自带的反解析工具)
javap -v className javap -v className > xxx.txt #输出到xxx.txt
使用IDEA插件jclasslib或jclasslib bytecode viewer客户端工具(可视化更好)
Class文件结构
官方文档
Class类的本质
- 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,Class文件实际上并不一定以磁盘文件的形式存在,Class文件是一组以8位字节为基础单位的二进制流
Class文件格式
- Class问价结构不像XML等描述语言,由于他没有任何分隔符号,所以在其中的数据项,无论是字节顺序还是数量,都是被严格限定的,那个字节代表什么含义,长度多少,先后顺序如何,都不允许改变
- Class文件格式采用一种类似于C语言结构体的方式进行数据存储,这种结构只有两种数据类型:无符号数和表
- 无符号数 属于基本的数据类型,以u1,u2,u4,u8分别代表1、2、4、8个字节长度的无符号数,无符号数可以用来描述数字、索引引用、数量值、或按照utf-8编码构成字符串值
- 表示由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以
_info
结尾,表用于描述有层次关系的复合结构数据,整个Class文件本质上就是一张表,由于表没有固定长度,所以通常会在其签名加上个数说明
代码举例
package com.ty.jvm;public class ClassDemo {private int num=1;public int add(){num=num+2;return num;} }
对应解析后的字节码文件
0 aload_01 aload_02 getfield #2 <com/ty/jvm/ClassDemo.num>5 iconst_26 iadd7 putfield #2 <com/ty/jvm/ClassDemo.num> 10 aload_0 11 getfield #2 <com/ty/jvm/ClassDemo.num> 14 ireturn
对应解析之前的字节码文件
概述
Class文件的结构并不是一成不变的,随着java虚拟机的发展,总是不可避免的会对Class文件结构做出一些调整,但其基本结构和框架是非常稳定的
Class文件的总体结构如下
- 魔数
- Class文件版本
- 常量池
- 访问标志(权限标识)
- 类索引、父类索引、接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
类型 | 名称 | 说明 | 长度 | 数量 |
---|---|---|---|---|
u4 | magic | 魔数,识别Class文件 格式 | 4个字节 | 1 |
u2 | minor_version | 副版本号(小版本) | 2个字节 | 1 |
u2 | major_version | 主版本号(大版本) | 2个字节 | 1 |
u2 | constant_pool_count | 常量池计数器 | 2个字节 | 1 |
cp_info | constant_pool | 常量池表 | n个字节 | constant_pool_count-1 |
u2 | access_flags | 访问标识 | 2个字节 | 1 |
u2 | this_class | 类索引 | 2个字节 | 1 |
u2 | super_class | 父类索引 | 2个字节 | 1 |
u2 | interfaces_count | 接口计数器 | 2个字节 | 1 |
u2 | interfaces | 接口索引集合 | 2个字节 | interfaces_count |
u2 | fields_count | 字段计数器 | 2个字节 | 1 |
field_info | fields | 字段表 | n个字节 | fields_count |
u2 | methods_count | 方法计数器 | 2个字节 | 1 |
method_info | methods | 方法表 | n个字节 | methods_count |
u2 | attributes_count | 属性计数器 | 2个字节 | 1 |
attribute_info | attributes | 属性表 | n个字节 | attributes_count |
魔数(magic)
每个Class文件开头的4个字节的无符号整数称为魔数
唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件,即:魔数就是Class文件的标识符
魔数值固定为0xCAFEBABE,不会改变
如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验时会抛出如下错误
使用魔数而不是扩展名来进行识别主要是出于安全方面的考虑,因为文件扩展名可以随意改动
Class文件版本(minor_version、major_version)
紧接着魔数的4个字节存储的是Class文件的版本号,前两个表示的是minor_version,后两个表示major_version
Class文件版本号和平台的对应
主版本(十进制) 副版本(十进制) 编译器版本 45 3 1.1 46 0 1.2 47 0 1.3 48 0 1.4 49 0 1.5 50 0 1.6 51 0 1.7 52 0 1.8 53 0 1.9 54 0 1.10 55 0 1.11 java的版本号是从45开始的,jdk1.1之后的每个JDK大版本发布的主版本号向上+1
不同版本的java编译器编译的Class文件对应的版本是不一样的,目前 高版本的java虚拟机可以执行低版本编译器生成的 Class文件,反之则不行,会抛出java.lang.UnsupportedClassVersionError异常
在实际应用中,由于开发版本和生成环境的不同,可能会导致该问题发生,
- 虚拟机JDK版本为1.k(k>=2)时,对应的class文件格式版本号的范围为45.0+k.0(含两端)
常量池
- 常量池是Class文件中内容最为丰富的区域之一,常量池对Class文件中的字段和方法解析也有着至关重要的作用
- 随着java虚拟机的不断发展,常量池的内容也日渐丰富,可以说常量池是整个Class文件的基石
- 在版本号之后紧跟的就是常量池的数量以及若干个常量池表项
- 常量池中的常量数量是不固定的,所以在常量池的入口处需要放置一项u2类型的无符号数,代表常量池容量计数器(constant_pool_count),这个容量计数是从1开始的,我们把常量池数据称为常量池集合
- 常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
常量池计数器
- 由于常量池的数量不固定,需要放置两个字节来表示常量池容量计数去值
- 常量池计数器:从1开始表示常量池中有多少项常量,即constant_pool_count=1表示常量池中有0个常量项
- 那么常量池集合中的第0项去哪里了?
- 为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,就把常量池的第0项给空出来的
- 在计算常量池项数时,把十六进制转换为十进制后如果数量为22,其实只有21个值是有效的,这个22包含了第0项,有效值的索引是从1-21
常量池表
- constant_pool是一种表结构,以1~constant_pool_count-1为索引,表明了后面有多少个常量项
- 常量池主要存放两大类常量:字面量和符号引用,还有句柄等
- 包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量,常量池中的每一项都具备相同的特征,第一个 字节作为类型标记,用于确定该项的格式,这个字节称为tag byte
- 常量类型和结构(简单版本,详细版本见最后附录)
类型 | 标志(或标识)-十进制 | 十六进制 | 描述 | 字节长度 |
---|---|---|---|---|
CONSTANT_utf8_info | 1 | 0x01 | UTF-8编码的字符串 | 3+length |
CONSTANT_Integer_info | 3 | 0x03 | 整型字面量 | 5 |
CONSTANT_Float_info | 4 | 0x04 | 浮点型字面量 | 5 |
CONSTANT_Long_info | 5 | 0x05 | 长整型字面量 | 9 |
CONSTANT_Double_info | 6 | 0x06 | 双精度浮点型字面量 | 9 |
CONSTANT_Class_info | 7 | 0x07 | 类或接口的符号引用 | 3 |
CONSTANT_String_info | 8 | 0x08 | 字符串类型字面量 | 3 |
CONSTANT_Fieldref_info | 9 | 0x09 | 字段的符号引用 | 5 |
CONSTANT_Methodref_info | 10 | 0x0a | 类中方法的符号引用 | 5 |
CONSTANT_InterfaceMethodref_info | 11 | 0x0b | 接口中方法的符号引用 | 5 |
CONSTANT_NameAndType_info | 12 | 0x0c | 字段或方法的符号引用 | 5 |
CONSTANT_MethodHandle_info | 15 | 0x0f | 表示方法句柄 | 4 |
CONSTANT_MethodType_info | 16 | 0x10 | 标志方法类型 | 3 |
CONSTANT_InvokeDynamic_info | 18 | 0x12 | 表示一个动态方法调用点 | 5 |
字面量和符号引用
常量 | 具体 |
---|---|
字面量 | 文本字符串 |
声明为final的常量值 | |
符号引用 | 类和接口的全限定名 |
字段的名称和描述符 | |
方法的名称和描述符 |
- 全限定名:com/ty/test/Demo就是这个类的全限定名。把包名的".“换成”/",为了使连续多个全限定名之间不产生混淆,在最后个通常会加一个";"表示全限定名的结束
- 简单名称:简单名称是指没有类型和参数修饰的方法或字段名称,比如add()方法的简单名称是add,int num的简单名称是num
- 描述符:描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型和返回值),根据描述符规则,基本数据类型已经代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L+对象的全限定名来表示
- 类型描述符
标志符 | 含义 |
---|---|
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 代表void类型 |
L |
对象类型,比如:Ljava/lang/Object;
|
[ |
数组类型,代表一维数组。比如:double[][][] is [[[D
|
- 举例:java.lang.String 的toString()方法的描述符为:Ljava/lang/String;
常量类型和结构解读(与附录A对应)
0a 00 04 00 12
为常量池集合的第一项,0a表示该常量项是CONSTANT_Methodref_info,00 04是指向CONSTANT_Class_info的索引项,即指向第四项,00 12是指向CONSTANT_NameAndType_info的索引项,即第十八项07 00 15
是第四项,07表示该常量项是CONSTANT_Class_info,00 15是指向全限定名常量项的索引项即第二十一项0c 00 07 00 08
是第十八项,0c表示该常量项是CONSTANT_NameAndType_info,00 07是指向名称的索引项即第七项,00 08是指向描述符的索引项即第八项01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
是第二十一项,01表示是一个CONSTANT_utf8_info,00 10表示该字符串长度是10,6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74是字符串值,转换为十进制对应ASCII表后得到该字符串常量为“java/lang/Object”01 00 06 3c 69 6e 69 74 3e
是第七项,01表示是一个CONSTANT_utf8_info,00 03表示该字符串长度是3,字符串值为<init>
01 00 03 28 29 56
是第八项。字符串值为()V- 综上()Vjava/lang/Object;
<init>
,得到的信息-》存在<init>
方法是java/lang/Object类的一个返回值为void的方法 - …
补充
Class文件不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中
访问标志(权限标识)
- 常量池表后紧跟的两个字节是访问标识,其值是标志值的累加,比如一个类的权限标识为public和final,那么这个访问标识的值是0x0011
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 标志为public类型 |
ACC_FINAL | 0x0010 | 标志被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | 标志允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法) |
ACC_INTERFACE | 0x0200 | 标志这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 |
ACC_SYNTHETIC | 0x1000 | 标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应) |
ACC_ANNOTATION | 0x2000 | 标志这是一个注解 |
ACC_ENUM | 0x4000 | 标志这是一个枚举 |
- 类的访问权限通常为以ACC_开头的常量
- 每一种类型的表示都是通过设置访问标记的32位中的特定位来实现的,比如 public final的类,则标记为ACC_PUBLIC | ACC_FINAL
- 使用ACC_SUPER可以让类更准确的定位到父类的方法super.method(),现代编译器都会设置并且使用这个标记
- 带有ACC_INTERFACE标志的class文件表示这是一个接口,不带此标识表示这是类
- 如果一个class文件被标识为ACC_INTERFACE,那么他也需要设置ACC_ABSTRACT,也就不能再被标识为ACC_FINAL、ACC_SUPER以及ACC_ENUM
- 如果没有ACC_INTERFACE,那么这个class文件可以被以上除ACC_ANNOTATION之外的其他所有标志标识,当然ACC_ABSTRACT和ACC_FINAL这两个互斥标志不能同时使用
- ACC_SUPER是用于确定类或接口里面的invokespecial指令使用的是哪一种执行语义,针对java虚拟机指令集的编译器都应该设置这个标志,对于javaSE 8及后续版本来说,无论class文件中这个标志的实际值是什么,也不管class文件的版本号是多少,java虚拟机都认为每个class文件均设置了ACC_SUPER标志
- ACC_SUPER标志是为了向后兼容就java编译器所编译的代码而设计的,目前的ACC_SUPER标志在有JDK1.0.2之前的 编译器所生成的access_flags中是没有确定含义的,如果设置了该标识,那么Oracle的 java虚拟机实现会将其忽略
- 注解类必须设置ACC_ANNOTATION以及ACC_INTERFACE
- ACC_ENUM表示该类或其父类是枚举类型
类索引、父类索引、接口索引集合
- 在访问标记后,会指定该类的类别、父类类别以及实现的 接口
长度 | 含义 |
---|---|
u2 | this_class,类索引 |
u2 | super_class,父类索引 |
u2 | interfaces_count,接口索引数量 |
u2 | interfaces[interfaces_count],接口索引集合 |
- 这三项数据来确定这个类的继承关系;
- 类索引用于确定这个类的全限定名
- 父类索引用于确定这个类的父类的全限定名,由于java父类不能多重继承,所以父类索引只有一个,出java.lang.Object之外,所有的java类都有父类,因此除java.lang.Object之外,所有类的父类索引都不为0
- 接口索引集合就用来描述这个类实现了那些接口,这些被实现的接口将按implements/extends语句的属性从左到右排列在接口索引集合中
- this_class,类索引
- 2字节无符号整数,指向常量池集合,提供了类的全限定名,值必须是对常量池表中某项的一个有效索引值,常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构图
- super_class,父类索引
- 2字节无符号整数,指向常量池集合,提供了父类的全限定名,值必须是对常量池表中某项的一个有效索引值,常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构图
- 没有继承父类时,默认继承java.lang.Object,只有一个
- 父类不能是final
- interfaces
- 指向常量池索引集合,提供了一个符号引用到所有已实现的接口
- 由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class_info
- interfaces_count,接口索引数量
- 2字节无符号整数,值表示接口索引集合大小
- interfaces[interfaces_count],接口索引集合
- 2字节无符号整数,地址长度为interfaces_count*2,数组长度为interfaces_count
- 每个成员值必须是对常量池表中某项的有效索引值,每个成员interfaces[i]必须为CONSTANT_Class_info,其中0<= i < interfaces_count
字段表集合
用于描述接口或类中什么的变量,保留类级变量以及实例变量,不包括方法内部,代码块内部声明的局部变量
字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述
指向常量池索引集合,描述了每个字段的完整信息,比如:字段的标识符、访问修饰符、是 类变量还是实例变量、是否是常量等
集合中不会出现父类或者 实现的接口中继承而来的字段,但有可能列出原本代码中不存在的字段,如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
在java语言中 字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的
fields表每个成员是一个fields_info结构的数据项:
- fields_info结构:修饰符都是布尔类型,要么有要么没有,比如final关键字,有就是不可变的,没有就是可变的
- 作用域(权限修饰符:public等)
- 是实例变量还是类变量(static修饰符)
- 可变性(final)
- 并发可见性(volatile,是否强制从主内存读写)
- 可否序列化(transient)
- 字段数据类型
- 字段名称
类型 名称 含义 数量 u2 access_flags 访问标志 1 u2 name_index 字段名索引 1 u2 descriptor_index 描述符索引 1 u2 attribute_count 属性计数器 1 attribute_info attributes 属性集合 attribute_count - fields_info结构:修饰符都是布尔类型,要么有要么没有,比如final关键字,有就是不可变的,没有就是可变的
字段访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
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_TRANSTENT | 0x0080 | 字段是否为transient |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
方法表集合
每一个methods_info都对应着一个类或接口中的方法信息,
如果这个方法部署抽象的或native的,那么字节码中会体现出来
methods只描述当前类声明的方法,不包括父类,有可能会出现编译器自动添加的方法,比如初始化方法
<init>
在java中要重载一个方法,除了要与 原方法名相同的简单名称之外,还要有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在 常量池中的字段符号引用的集合,也就是因为 返回值不会包含在特征签名中,因此java语言 无法仅仅一卡 返回值不同来对一个已有的方法进行重载,但在Class文件中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存,也就是说,如果量方法有相同名称和特征签名,但返回值不同,可以合法共存于一个class文件中
尽管java语法规范不允许在一个类或接口中声明多个方法签名相同的方法,但是字节码文件汇总允许存放,唯一 条件就是返回值不能相同
methods_info结构:类似于字段表,
类型 名称 含义 数量 u2 access_flags 访问标志 1 u2 name_index 字段名索引 1 u2 descriptor_index 描述符索引 1 u2 attribute_count 属性计数器 1 attribute_info attributes 属性集合 attribute_count 访问标志,public等标识与字段表相同,不同部分如下
标志名称 标志值 含义 ACC_SYNCHRONIZED 0X0020 synchronize,线程同步 ACC_BRIDGE 0x0040 bridge方法由编译器产生 ACC_VARARGS 0x0080 可变数量的参数声明 ACC_NATIVE 0x0100 是否为本地方法 ACC_ABSTRACT 0x0400 抽象方法 ACC_STRICT 0x0800 严格模式 ACC_SYNTHETIC 0x1000 是否是编译器自动产生
属性表集合
两个作用:
描述字段或方法的属性信息
- 一个字段的初始值,一些注释信息等就是该字段的属性,对常量属性 而言attribute_length恒为2
描述class文件所携带的辅助信息
- 比如class文件的源文件的名称,已经任何带有RetentionPolicy.CLASS或RetentionPolicy.RUNTIME的注解,这类信息一般用于java虚拟机的验证和运行,以及java程序的调试
属性表不要求有严格的顺序,只要不予已有属性名重复,任何人实现的编译器都可以向属性表中写入自定义的属性信息,虚拟机运行时会忽略掉不认识的属性
attribute_info结构
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u1 | info | attribute_length | 属性表 |
属性类型:
- java8定义了23种属性,这23种属性每个都有各自的格式,详情看官方文档,这里就不写了
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量池 |
Deprecated | 类、方法、字段表 | 被声明为deprecated的方法和字段 |
Exception | 方法表 | 方法抛出异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或匿名类时有这个属性,用于标识类所在的外围方法 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | java源码的行号与字节码指令对应的关系 |
StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配 |
Synthetic | 类、方法、字段表 | 标志方法或字段为编译器自动生成的 |
Signature | 类、方法、字段表 | 用于支持泛型情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | 用于存储额外的调试信息 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
LocalVariableTypeTable | Code属性 | 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类、方法、字段表 | 为动态注解提供支持 |
RuntimeInvisibleAnnotations | 类、方法、字段表 | 用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotations | 方法表 | 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法 |
RuntimeInvisibleParameterAnnotations | 方法表 | 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数 |
RuntimeVisibleTypeAnnotations | 类、方法、字段表、Code属性 | 记录在相应类,字段或方法的声明中或在相应方法主体中的表达式中使用的类型上的运行时可见注释 |
RuntimeInvisibleTypeAnnotations | 类、方法、字段表、Code属性 | 记录有关在类,字段或方法的相应声明中或在相应方法主体中的表达式中使用的类型的运行时不可见注释 |
AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | 用于保存invokeddynamic指令引用的引导方式限定符 |
MethodParameters | 方法表 | 记录了一个方法的形式参数,如他们的名字信息 |
code格式
demo二进制解析
源代码:
package com.ty.jvm;public class ClassDemo {private int num=1;public int add(){num=num+2;return num;}
}
二进制解析:
ca fe ba be
: 魔数
00 00
:副版本
00 34
:主版本:52
00 16
:常量池计数器,0x0016-> 共22-1项
白底部分
:常量池
0a 00 04 00 12/09 00 03 00 13/07 00 14/07 00 15/01 00 03 6e 75 6d/01 00 01 49/01 00 06 3c 69 6e 69 74 3e/01 00 03 28 29 56/01 00 04 43 6f 64 65/01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65/01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 /01 00 04 74 68 69 73/01 00 16 4c 63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f 3b/01 00 03 61 64 64/01 00 03 28 29 49/01 00 0a 53 6f 75 72 63 65 46 69 6c 65 /01 00 0e 43 6c 61 73 73 44 65 6d 6f 2e 6a 61 76 61/0c 00 07 00 08/0c 00 05 00 06/01 00 14 63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f/01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
0a 00 04 00 12
:0a-> CONSTANT_Methidref_info, 00 04->指向第四项,00 12->指向第十八项
09 00 03 00 13
: 09-> CONSTANT_Fieldref_info, 00 03->指向第三项,00 13->指向第十九项
07 00 14
: 07-> CONSTANT_Class_info, 00 14-> 指向第二十项
07 00 15
: 07-> CONSTANT_Class_info, 00 14-> 指向第二十一项
01 00 03 6e 75 6d
: 01->CONSTANT_utf8_info, 00 03->占3个字节,6e 75 6d->num
01 00 01 49
:01->CONSTANT_utf8_info, 00 01->占1个字节,49->I(大写I不是1)
01 00 06 3c 69 6e 69 74 3e
:01->CONSTANT_utf8_info, 00 06 ->占三个6节,3c 69 6e 69 74 3e-><init>
01 00 03 28 29 56
:01->CONSTANT_utf8_info, 00 03->占3个字节,28 29 56->()V
01 00 04 43 6f 64 65
:01->CONSTANT_utf8_info, 00 03-4占4个字节,43 6f 64 65->Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65
:01->CONSTANT_utf8_info, 00 0f->占15个字节,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65->LineNumberTable
01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65
: 01->CONSTANT_utf8_info, 00 12->占18个字节,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65->LocalVariableTable
01 00 04 74 68 69 73
: 01->CONSTANT_utf8_info, 00 04->占4个字节,74 68 69 73->this
01 00 16 4c 63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f 3b
:01->CONSTANT_utf8_info, 00 16->占22个字节,4c 63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f 3b->Lcom/ty/jvm/ClassDemo;
01 00 03 61 64 64
: 01->CONSTANT_utf8_info, 00 03->占3个字节,61 64 64->add
01 00 03 28 29 49
: 01->CONSTANT_utf8_info, 00 03->占3个字节,28 29 49->()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65
: 01->CONSTANT_utf8_info, 00 0a->占10个字节,53 6f 75 72 63 65 46 69 6c 65->SourceFile
01 00 0e 43 6c 61 73 73 44 65 6d 6f 2e 6a 61 76 61
: 01->CONSTANT_utf8_info, 00 0e->占14个字节,43 6c 61 73 73 44 65 6d 6f 2e 6a 61 76 61->ClassDemo.java
0c 00 07 00 08
: 0c-> CONSTANT_NameAndType_info, 00 07指向第七项,00 08指向第八项
0c 00 05 00 06
:0c-> CONSTANT_NameAndType_info, 00 05指向第五项,00 06指向第六项
01 00 14 63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f
: 01->CONSTANT_utf8_info, 00 14->占20个字节,63 6f 6d 2f 74 79 2f 6a 76 6d 2f 43 6c 61 73 73 44 65 6d 6f->com/ty/jvm/ClassDemo
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
: 01->CONSTANT_utf8_info, 00 10->占16个字节,6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74->java/lang/Object
00 21
: 访问标志,0x0020+0x0001->ACC_PUBLIC+ACC_SUPER
00 03 00 04 00 00
: 索引集合,00 03->类索引,指向常量项第三项,00 04-> 父类索引,指向常量项第四项,00 00->接口索引数量为0
00 01
: 00 01 字段表计数器
00 02 00 05 00 06 00 00
: 字段表集合, 访00 02问标志ACC_PUBLIC,00 05字段名索引指向第五项,00 06
描述符索引指向第六项,00 00属性表计数器
00 02
: 方法表计数器
方法表集合
: 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0a 2a b7 00 01 2a 04 b5 00 02 b1 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 03 00 04 00 04 00 0b 00 00 00 0c 00 01 00 00 00 0a 00 0c 00 0d 00 00 00 01 00 0e 00 0f 00 01 00 09 00 00 00 3d 00 03 00 01 00 00 00 0f 2a 2a b4 00 02 05 60 b5 00 02 2a b4 00 02 ac 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 07 00 0a 00 08 00 0b 00 00 00 0c 00 01 00 00 00 0f 00 0c 00 0d 00 00
00 01
: 访问标志 ACC_PUBLIC
00 07
: 方法名索引指向第七项
00 08
: 描述符索引,指向第八项
00 01
: 属性计数器
00 09
: 属性名索引指向9项
00 00 00 38
: 属性长度38字节
00 02
: 操作数栈最大深度
00 01
: 局部变量表所需的存续空间
00 00 00 0a
: 字节码指令长度10字节
2a b7 00 01 2a 04 b5 00 02 b1
: 字节码指令
00 00
: 异常表长度
00 02
: 属性计数器
00 0a
: 属性名索引指向第10项
00 00 00 0a
: 属性长度10字节
00 02
: start_pc
00 00
: 长度
00 03
: 名称索引
00 04
: 描述符索引
00 04
: 索引
00 09 00 00 00 3d 00 03 00 01 00 00 00 0f 2a 2a b4 00 02 05 60 b5 00 02 2a b4 00 02 ac 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 07 00 0a 00 08 00 0b 00 00 00 0c 00 01 00 00 00 0f 00 0c 00 0d 00 00
:同上
00 01
: 访问标志 ACC_PUBLIC
00 0e
: 方法名索引指向第14项
00 0f
: 描述符索引,指向第15项
00 01
: 属性计数器
00 01 00 10 00 00 00 02 00 11
:属性表集合
说明:
类型 | 默认初始值 |
---|---|
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
char | \u0000 |
boolean | false |
reference | null |
javap指令解析Class文件
#javac命令相关参数
-g #生成局部变量表,eclipse和IDEA会自动生成#javap命令相关参数
-help --help -? 输出此用法消息
-version 当前javap所在jdk的版本信息-package 显示程序包/受保护的/公共类和成员 (默认)
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-p -private 显示所有类和成员-v -verbose 输出附加信息
-l 输出行号和本地变量表
-c 对代码进行反汇编
-s 输出内部类型签名-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
附录A:常量类型和结构(详细版)
说明:CONSTANT_utf8_info的bytes占的长度是length的长度,所以CONSTANT_utf8_info的总长度应该是1+2+length的大小
JVM-13-Class文件结构相关推荐
- JVM学习-类文件结构
1.类加载与字节码技术 类加载与字节码技术包括以下内容 1.类文件结构 2.字节码指令 3.编译期处理 4.类加载阶段 5.类加载器 6.运行期优化 本篇博文仅讲解类文件结构,余下的可以参看JVM系列 ...
- 修改 class_带你探索JVM的Class文件结构
魔数: 大多数情况下,我们都是通过扩展名来识别一个文件的类型的,比如我们看到一个.txt类型的文件我们就知道他是一个纯文本文件.但是,扩展名是可以修改的,那一旦一个文件的扩展名被修改过,那么怎么识别一 ...
- jvm(13)-线程安全与锁优化
[0]README 0.1)本文部分文字转自"深入理解jvm", 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结 ...
- JVM——13.定位 StackOverflowError
文章目录 1. Java虚拟机栈和方法调用 2. 什么情况会发生 StackOverflow 3. 模拟 StackOverflowError 4. StackOverflowError 的定位及解决 ...
- JVM Class 类文件结构 (系列号2)
Class 文件的基本介绍 class 文件是一组以8字节为基本单位的二进制流.各个数据按照顺序紧凑的排列在class文件之中.遇到大于8字节的数据会分成若干个8位数据. 下图就是class文件中的数 ...
- 欧尼酱讲JVM(13)——本地方法栈
位置图解 本地方法栈在运行时数据区中,三个灰色的部分是线程私有的. 本地方法栈作用 Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用. 本地方法栈是线程私有了,允许被线程 ...
- 【Java虚拟机】万字长文,搞定JVM方方面面!
文章目录 1.JVM内存结构 1.1.JVM内存结构图 1.2.程序计数器 1.3.虚拟机栈 1.4.本地方法栈 1.5.Java堆 1.6.方法区 1.7.StringTable 1.8.直接内存 ...
- 深入理解JVM文章合集
原文地址:http://ddrv.cn/a/88331 Java动态追踪技术探究 在Java虚拟机中,字符串常量到底存放在哪 一次生产 CPU 100% 排查优化实践 聊聊 Java 虚拟机:类的加载 ...
- <JVM笔记:内存与垃圾回收>13-垃圾回收器
13. 垃圾回收器 13.1. GC 分类与性能指标 13.1.1. 垃圾回收器概述 13.1.2. 垃圾收集器分类 13.1.3. 评估 GC 的性能指标 13.2. 不同的垃圾回收器概述 13.2 ...
- <JVM上篇:内存与垃圾回收篇>13-垃圾回收器
13. 垃圾回收器 13.1. GC 分类与性能指标 13.1.1. 垃圾回收器概述 13.1.2. 垃圾收集器分类 13.1.3. 评估 GC 的性能指标 吞吐量 暂停时间 吞吐量 vs 暂停时间 ...
最新文章
- 【 FPGA 】状态机的模型之Moore型状态机
- Acdream Xor 简单数学
- 玻璃体定点注入(个人猜想)
- c语言函数声明定义参数命名,C语言函数声明与定义
- 触屏网站如何实现返回并刷新
- django 1.8 官方文档翻译: 3-1-3 Django 的快捷函数
- 说说VNode节点(Vue.js实现)
- 阿里巴巴大数据实践:大数据建设方法论OneData
- re模块 match serach findall 详解
- 2018年广发证券信息技术部面试总结
- PyQt5 打造GUI爬虫 小说下载器
- Android Kernel wakeup_sources分析
- MAC上有哪些优秀的日常软件| 入门级Mac OS 用户必备软件
- 协同感知综述:从异质单体到分层合作
- 2011版MacBook Air win7安装教程
- 微软开源!世界首个AI量化投资平台 Qlib 基本使用教程
- HaaS轻应用(JavaScript)低功耗蓝牙案例
- 验证码识别的原理python_蓝奏云数值验证码识别,python调用虹鱼图灵识别插件,超高正确率...
- 手动制造报错_错误消息 : 检测到计算机制造商显卡驱动程序
- kali grub引导修复