字节码剖析
示例代码:
package com.leofight.jvm.bytecode;
public class MyTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
反编译信息如下:
classes javap -verbose com.leofight.jvm.bytecode.MyTest1
Classfile /Users/lz/IdeaProjects/jvm_lecture/out/production/classes/com/leofight/jvm/bytecode/MyTest1.class
Last modified Jul 29, 2018; size 497 bytes
MD5 checksum 962ee95ed59fdba7d6d90375419bc0ff
Compiled from "MyTest1.java"
public class com.leofight.jvm.bytecode.MyTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/leofight/jvm/bytecode/MyTest1.a:I
#3 = Class #22 // com/leofight/jvm/bytecode/MyTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/leofight/jvm/bytecode/MyTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/leofight/jvm/bytecode/MyTest1
#23 = Utf8 java/lang/Object
{
public com.leofight.jvm.bytecode.MyTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/leofight/jvm/bytecode/MyTest1;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/leofight/jvm/bytecode/MyTest1;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 13: 0
line 14: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/leofight/jvm/bytecode/MyTest1;
0 6 1 a I
}
SourceFile: "MyTest1.java"
字节码文件16进制
CAFEBABE 00000034 00180A00 04001409 00030015 07001607 00170100 01610100 01490100 063C696E 69743E01 00032829 56010004 436F6465 01000F4C 696E654E 756D6265 72546162 6C650100 124C6F63 616C5661 72696162 6C655461 626C6501 00047468 69730100 234C636F 6D2F6C65 6F666967 68742F6A 766D2F62 79746563 6F64652F 4D795465 7374313B 01000467 65744101 00032829 49010004 73657441 01000428 49295601 000A536F 75726365 46696C65 01000C4D 79546573 74312E6A 6176610C 00070008 0C000500 06010021 636F6D2F 6C656F66 69676874 2F6A766D 2F627974 65636F64 652F4D79 54657374 31010010 6A617661 2F6C616E 672F4F62 6A656374 00210003 00040000 00010002 00050006 00000003 00010007 00080001 00090000 00380002 00010000 000A2AB7 00012A04 B50002B1 00000002 000A0000 000A0002 00000003 00040005 000B0000 000C0001 0000000A 000C000D 00000001 000E000F 00010009 0000002F 00010001 00000005 2AB40002 AC000000 02000A00 00000600 01000000 09000B00 00000C00 01000000 05000C00 0D000000 01001000 11000100 09000000 3E000200 02000000 062A1BB5 0002B100 00000200 0A000000 0A000200 00000D00 05000E00 0B000000 16000200 00000600 0C000D00 00000000 06000500 06000100 01001200 00000200 13
Java字节码结构
Java字节码整体结构
完整的Java字节码结构
Class字节码中有两种数据类型:
- 字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。
- 表(数组):表是由多个基本数据或其他表,既按照既定顺序组成的大的数据结合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是严格定义好的。
使用javap -verbose
命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。
魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。
版本号::魔数之后的4个字节为版本信息,前两个字节表示minor version
(次版本号),后两个字节表示major version
(主版本号),这里的版本号为 00 00 00 34,换算成十进制,表示次版本号为0,主版本号为52。所以该文件的版本号为:1.8.0。可以通过java -version
来验证这一点。
➜ classes java -version
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
常量池(constant pool):紧接着主版本号之后 就是常量池的入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。
常量池的总体结构: Java类所对应的常量池主要由常量池数量与常量池数组(常量表)这两部分公共组成。常量池数量仅跟在主版本号后面,占据2个字节;常量池数组仅跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每一种元素的第一个数据都是u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值的注意的是,常量池数组中元素的个数 = 常量池数 - 1 (其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它位于常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始。
在上面的表中描述了11种数据类型的结构,其实在jdk1.7之后又增加了3种(CONSTANT_MethodHandle_info,CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)。这样一共是14种。
在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 getRealnamebyIdAndNickname(int id,String name)
的描述符为:(I,Ljava/lang/String;)Ljava/lang/String;
Access_Flag 访问标志:访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final。通过上面的源代码,我们知道该文件是类并且是public。
0x 00 21:是0×0020和0×0001的并集,表示ACC_PUBLIC与ACC_SUPER 。
类索引:用于确定类的全限定名,0×00 03 表示引用第3个常量,同时第3个常量引用第22个常量,查找得#22 = Utf8 com/leofight/jvm/bytecode/MyTest1
父类索引:0×00 04 同理:#4.#23(java/lang/Object)
接口索引:接口有2+n个字节,前两个字节表示的是接口数量,后面跟着就是接口的表。我们这个类没有任何接口,所以应该是0000。
字段表集合:字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
0×00 02 :访问标志为private
0×00 05 : 字段名称索引为#5,对应的是”a”
0x 00 06 :描述符索引为#6,对应的是”I”
0x 00 00 :属性表数量为0,因此没有属性表。
方法表:
方法中的每个属性都是一个attribute_info结构
JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,共运行时使用。不同的attribute通过attribute_name_index来区分。
Code结构:Code attribute的作用是保存该方法的结构,如所对应的字节码
attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。
max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数局部变量。
code_length表示该方法所包含的字节码的字节数以及具体的指令码
具体字节码即是该方法被调用时,虚拟机所执行的字节码
exception_table,这里存放的是处理异常的信息
每个exception_table表项由start_pc,end_pc,handler_pc,catch_type组成。
start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理
handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常。
附加属性
LineNumberTable:这个属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。
网友评论