您的位置:首页 > 编程语言 > Java开发

Java字节码详解系列之二--解析字节码

2018-04-03 16:41 288 查看
1、javap查看字节码内容上文介绍了字节码的结构,本文主要通过一个简单的例子说明class字节码的每一个字段。package com.zcm.test;

import java.io.Serializable;

public class SourceTest implements Serializable{
public int a=3;
static Integer b=6;
String s="Hello World!";

public static void main(String[] args){
SourceTest test=new SourceTest();
test.a=9;
b=8;
}
private String test(){
return s;
}
}使用javac -d SourceTest.java 编译该文件,生成SourceTest.class。
使用javap -verbose SourceTest 就会分析出该class的字节码内容Classfile /Users/zcm/Downloads/com/zcm/test/SourceTest.class
Last modified 2018-4-2; size 694 bytes
MD5 checksum 5874dd9682f495b853d6ccb7b8af652b
Compiled from "SourceTest.java"
public class com.zcm.test.SourceTest implements java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#29 // com/zcm/test/SourceTest.a:I
#3 = String #30 // Hello World!
#4 = Fieldref #5.#31 // com/zcm/test/SourceTest.s:Ljava/lang/String;
#5 = Class #32 // com/zcm/test/SourceTest
#6 = Methodref #5.#28 // com/zcm/test/SourceTest."<init>":()V
#7 = Methodref #33.#34 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#8 = Fieldref #5.#35 // com/zcm/test/SourceTest.b:Ljava/lang/Integer;
#9 = Class #36 // java/lang/Object
#10 = Class #37 // java/io/Serializable
#11 = Utf8 a
#12 = Utf8 I
#13 = Utf8 b
#14 = Utf8 Ljava/lang/Integer;
#15 = Utf8 s
#16 = Utf8 Ljava/lang/String;
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 test
#24 = Utf8 ()Ljava/lang/String;
#25 = Utf8 <clinit>
#26 = Utf8 SourceFile
#27 = Utf8 SourceTest.java
#28 = NameAndType #17:#18 // "<init>":()V
#29 = NameAndType #11:#12 // a:I
#30 = Utf8 Hello World!
#31 = NameAndType #15:#16 // s:Ljava/lang/String;
#32 = Utf8 com/zcm/test/SourceTest
#33 = Class #38 // java/lang/Integer
#34 = NameAndType #39:#40 // valueOf:(I)Ljava/lang/Integer;
#35 = NameAndType #13:#14
4000
// b:Ljava/lang/Integer;
#36 = Utf8 java/lang/Object
#37 = Utf8 java/io/Serializable
#38 = Utf8 java/lang/Integer
#39 = Utf8 valueOf
#40 = Utf8 (I)Ljava/lang/Integer;
{
public int a;
descriptor: I
flags: ACC_PUBLIC

static java.lang.Integer b;
descriptor: Ljava/lang/Integer;
flags: ACC_STATIC

java.lang.String s;
descriptor: Ljava/lang/String;
flags:

public com.zcm.test.SourceTest();
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_3
6: putfield #2 // Field a:I
9: aload_0
10: ldc #3 // String Hello World!
12: putfield #4 // Field s:Ljava/lang/String;
15: return
LineNumberTable:
line 5: 0
line 6: 4
line 8: 9

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/zcm/test/SourceTest
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 9
11: putfield #2 // Field a:I
14: bipush 8
16: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: putstatic #8 // Field b:Ljava/lang/Integer;
22: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 14
line 14: 22

static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 6
2: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: putstatic #8 // Field b:Ljava/lang/Integer;
8: return
LineNumberTable:
line 7: 0
}
SourceFile: "SourceTest.java"
通过javap -verbose命令可以看到字节码所包含的信息。
2、查看字节码二进制在Mac系统下可以通过vim查看class文件,键入:%!xxd使用十六进制查看



3、class文件结构详解魔数所有的.class字节码文件的开始4个字节都是魔数,并且其值一定是0xCAFEBABE。如果该值不是0xCAFEBABE,则JVM拒绝加载该文件。



版本号魔数之后的4个字节为版本信息,前2个字节表示major version,即主版本号;后2个字节表示minor version,即次版本号。
这里的版本号值为0x00000034对应的十进制数是52,这里我用Java1.8编译出来的,其值是52。如果class文件的版本号超出了虚拟机支持的最高版本号,JVM将抛出java.lang.UnsupportedClassVersionError异常。



常量池Java类中的绝大多数信息都由常量池描述,常量池主要由常量池数量和常量池数组俩部分组成。常量池数量紧跟在次版本号的后面,占2个字节,class字节码的排列非常紧凑。而常量池数组紧跟在常量池数量之后。
每一个常量池元素都由两部分组成:tag和数组内容。使用结构化的方式来描述常量池数组的编排,描述如下:
tag1元素内容tag2元素内容tag3元素内容tag4元素内容....
常量池一共定义了11中常量,如下表:
JVM常量表
编号  常量池元素名称tag位标识含义
1CONSTANT_Utf8_info1UTF-8编码的字符串
2CONSTANT_Integer_info   3整型字面量
3CONSTANT_Float_info  4浮点型字面量
4CONSTANT_Long_info5长整型字面量
5CONSTANT_Double_info   6双精度字面量
6CONSTANT_Class_info7类或接口的符号引用
7CONSTANT_String_info 8字符串类型的字面量
8CONSTANT_Fieldref_info9字段的符号引用
9CONSTANT_Methodref_info   10类中方法的符号引用
10CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
11CONSTANT_NameAndType_info 12字段和方法的名称以及类型的符号引用
根据前面介绍的字节码格式,紧接着的第9,10字节是常量池的数量,后面是常量池数组内容。
下面给出JVM所定义的常量池中每一种元素的具体结构。

CONSTANT_Utf8_info
类型
结构
说明
u1
tag
CONSTANT_Utf8 (1)
u2
length
bytes所代表的字符串的长度
u1
bytes[length]
字符串的byte数据
CONSTANT_Integer_info
类型
结构
说明
u1
tag
CONSTANT_Integer (3)
u4
bytes
整型常量值
CONSTANT_Float_info
类型
结构
说明
u1
tag
CONSTANT_Float(4)
u4
bytes
单精度浮点型常量值
CONSTANT_Long_info
类型
结构
说明
u1
tag
CONSTANT_Long (5)
u4
high_bytes
长整型的高四位值
u4
low_bytes
长整型的低四位值
CONSTANT_Double_info
类型
结构
说明
u1
tag
CONSTANT_Double(6)
u4
high_bytes
双精度浮点的高四位值
u4
low_bytes
双精度浮点的低四位值
CONSTANT_Class_info format
类型
结构
说明
u1
tag
CONSTANT_Class (7)
u2
name_index
constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。
CONSTANT_String_info
类型
结构
说明
u1
tag
CONSTANT_String(8)
u2
string_index
constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。
CONSTANT_Fieldref_info
类型
结构
说明
u1
tag
CONSTANT_Fieldref(9)
u2
class_index
constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。
u2
name_and_type_index
constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。
CONSTANT_Methodref_info
类型
结构
说明
u1
tag
CONSTANT_Methodref(10)
u2
class_index
constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。
u2
name_and_type_index
constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。
CONSTANT_InterfaceMethodref_info
类型
结构
说明
u1
tag
CONSTANT_InterfaceMethodref(11)
u2
class_index
constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。
u2
name_and_type_index
constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。
CONSTANT_NameAndType_info
类型
结构
说明
u1
tag
CONSTANT_NameAndType (12)
u2
name_index
constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。
u2
descriptor_index
constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的描述符
接着上面的分析,后面跟在常量池数量后面的就是常量池数组,按照上面JVM常量表的顺序排列。这里就不一一描述了。
访问标识 access_flags常量池后面紧跟的就是access_flags结构,该结构占2个字节,类型是u2。代表访问标志位,用于标注类或接口的访问信息。access_flags的可选值如下: 

标志名称含义
ACC_PUBLIC0×0001是否为pubilc类型
ACC_FINAL0×0010是否为final类型,只有类可设置
ACC_SUPER0×0020用于兼容早期编译器,新编译器都设置该标记,以在使用invokespecial指令时对子类方法做特定处理。
ACC_INTERFACE0×0200标识为接口。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM
ACC_ABSTRACT0×0400抽象类,无法实例化。不可与ACC_FINAL同时设置。
ACC_SYNTHETIC0×1000synthetic,由编译器产生,不存在于源代码中。
ACC_ANNOTATION0×2000注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT
ACC_ENUM0×4000枚举类型
this_class该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录当前类的全限定名。
super_class该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录父类的全限定名。
interfacesinterfaces包含interfaces_count和interfaces[interfaces_count]。interfaces_count类型是u2,占2个字节。记录当前类所实现的接口数量。
interfaces[interfaces_count]是一组u2类型数据的集合。记录了当前类实现了哪些接口。如果没有实现过接口,就没有interface这一项。
field_infofields_count结构同上,记录了当前类所定义的变量的总数量。包括类成员变量和类变量(静态变量)。
fields[fields_count]这里记录类中所定义的各个变量的详细信息,包括变量名,变量类型,访问标识,属性等。
field_info
类型
名称
说明
u2
access_flags
记录字段的访问权限。
u2
name_index
constant_pool中的索引,CONSTANT_Utf8_info类型。表示变量的名称引用。
u2
descriptor_index
constant_pool中的索引,CONSTANT_Utf8_info类型,表示变量的类型信息引用。
u2
attributes_count
attributes包含的项目数。
attribute_info
attributes[attributes_count]
字段中包含的Attribute集合。
其中access_flags有如下的可选项。
字段的访问权限
标志名称含义
ACC_PUBLIC0x0001pubilc,包外可访问。
ACC_PRIVATE0x0002private,只可在类内访问。
ACC_PROTECTED0x0004protected,类内和子类中可访问。
ACC_STATIC0x0008static,静态。
ACC_FINAL0x0010final,常量。
ACC_VOILATIE0x0040volatile,直接读写内存,不可被缓存。不可和ACC_FINAL一起使用。
ACC_TRANSIENT0x0080transient,在序列化中被忽略的字段。
ACC_SYNTHETIC0x1000synthetic,由编译器产生,不存在于源代码中。
ACC_ENUM0x4000enum,枚举类型字段
method_infomethod_info包含methods_count和methods[methods_count];methods_count类型是u2,占2个字节。描述类中共包含多少个方法。methods_count后面是methods数组。该数组格式如下表:

methods结构
类型
名称
说明
u2
access_flags
记录方法的访问权限。
u2
name_index
constant_pool中的索引,CONSTANT_Utf8_info类型。指定方法名称。
u2
descriptor_index
constant_pool中的索引,CONSTANT_Utf8_info类型,指定方法的描述符。
u2
attributes_count
attributes包含的项目数。
attribute_info
attributes[attributes_count]
字段中包含的Attribute集合。
methods数组各项结构与fields类似,这里不再赘述。
其中,access_flags的可选值如下:

方法的访问权限
标志名称标志值说明
ACC_PUBLIC0x0001pubilc,包外可访问。
ACC_PRIVATE0x0002private,只可在类内访问。
ACC_PROTECTED0x0004protected,类内和子类中可访问。
ACC_STATIC0x0008static,静态。
ACC_FINAL0x0010final,不可被重写。
ACC_SYNCHRONIZED0x0020synchronized,同步方法。
ACC_BRIDGE0x0040bridge方法,由编译器生成。
ACC_VARARGS0x0080包含不定参数个数的方法。
ACC_NATIVE0x0100native,非Java语言实现的方法。
ACC_ABSTRACT0x0400abstract,抽象方法。
ACC_STRICT0x0800strictfp,设置floating-point模式为FP-strict。
ACC_SYNTHETIC0x1000synthetic,由编译器产生,不存在于源代码中。
Attribute属性表集合
在class文件中,属性表,方法表中都可以包含自己的属性表,用于描述某些场景的专有信息。为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的几种属性,除此以外,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。
属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量值
Deprecated类文件,字段表,方法表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常
InnerClasses类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
SourceFile类文件源文件名称
Synthetic类文件,字段表,方法表标识方法或字段是由编译器自动生成的
限于篇幅,每种属性的结构就不介绍了。感兴趣的同学可以查看JVM官方文档。      到此,我们分析了一个具体的Java程序生成的字节码文件的格式和内容。了解class文件结构可以帮助我们更深入的理解JVM的运行原理。本文限于篇幅,很多细节的地方没有分析到位,但是JVM定义的内容大致都有介绍,详细的信息大家可以参考JVM官方文档。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: