使用java -verbose 命令分析一个字节码文件时,
将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、
类中的方法信息、类变量与成员变量等信息。
字节码结构表
Java字节码整体结构
字节码结构表
魔数
所有的.class字节码文件的前4个字节都是魔数。魔数值为固定的值:0xCAFEBABE(James Gosling定义的常量)。
版本信息
魔术之后的4个字节是版本信息。前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。
这里的版本号为00 00 00 34,十六进制换算成十进制表示,次版本号为0,主版本号为52(3*16+4)(52对应jdk1.8,51对应jdk1.7)。所以该文件的版本号为1.8.0。可以通过java -version来验证。
常量池
在主版本号后,就是常量池(constant pool)入口。一个Java类定义的很多信息都是由常量池来维护和描述的。我们可以将常量池看做是class文件的资源仓库。比如说:java类中定义的方法与变量信息,都是存储在常量池中。
常量池中主要存储两类常量:字面量和符号引用。字面量如:文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。
字节码结构表
常量池的总体结构:
Java类所对应的常量池主要由常量池数量和常量池数组这两部分共同组成。常量池数量紧跟在主版本号后面,占据两个字节。而常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度也不同。但是每一种元素的第一个数据都是一个u1类型,该字节是标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用)。目的是满足某些常量池索引值的数据在特定情况下需要表达『不引用任何一个常量池』的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量对应null值;所以常量池的索引从1而非0开始。
在JVM规范中,每个变量/字段都有描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型JVM都只使用一个大写字母来表示:B:byte,C:char,D:double,F:float,I:int,J:long,S:short,Z:boolean,V:void,L:对象类型。如Ljava/lang/String;
对于数组类型来说,每一个纬度都使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;
用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法String getRealName(int id, String name)的描述符为:(I,Ljava/lang/String;) Ljava/lang/String;
Class字节码中有两种数据类型:
- 字节数据直接量:这是基本的数据类型。共细分为u1,u2,u4,u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。
- 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。
Access_Flag 访问标志
访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abstrract,如果是类,是否被声明成final。通过上面的源代码,我们知道该文件是类并且是public。
Class access and property modifiers
栈帧(stack frame)
栈帧是一种用于帮助虚拟机执行方法调用与方法执行的数据结构。
栈帧本身是一种数据结构,封装了方法的局部变量表、动态链接信息、方法的返回地址和操作树栈等信息。
符号引用、直接引用
有些符号引用是在类加载阶段或是第一次使用时就会转换为直接引用,这种转换叫做静态解析;另外一些符号引用则是在每次运行期间转换为直接引用,这类转换叫做动态链接,这也提现了java的多态性。
invokeinterface:调用接口中的方法,实际上是在运行期决定的,决定到底调用哪个对象的特定方法。
invokestatic:调用静态方法。
invokespecial:调用自己的私有方法、构造方法(<init>)以及父类的方法。
invokevirtual:调用虚方法,运行期动态查找的过程。
invokedynamic:动态调用方法。
静态解析的4中情形:
1、静态方法
2、父类方法
3、构造方法
4、私有方法(无法被重写)
以上4类方法成为非虚方法,他们是在类加载阶段就可以将符号引用转换为直接引用的。
基于栈的指令集与基于寄存器的指令集之间的关系:
1、JVM执行指令时所采取的方式是基于栈的指令集;
2、基于栈的指令集主要的操作有入栈与出栈两种;
3、基于栈的指令集的优势在于它可以在不同的平台之间移植,而基于寄存器的指令集是与硬件架构紧密关联的,无法做到可移植。
4、基于栈的指令集的缺点在于完成相同的操作,指令数量通常要比基于寄存器的指令集数量要多;基于栈的指令集是在内存中完成操作的,基于寄存器的指令集是直接由CPU来执行的,它是在告诉缓冲区中进行执行的,速度要快很多。虽然虚拟机可以采用一些优化手段,但总体来说,基于栈的指令集的执行速度要慢一些。











网友评论