简要说明 Java 中 .class 文件的内部结构

了解 .class 的文件结构,有助于加深对 Java 语言的理解和程序的优化。特别是深入了解之后,可以从原理上理解 Java 语言的很多底层的技术。
[url=http://ezerg.iteye.com/blog/1069555]针对上一次利用 ASM 修改字节码的内容[/url],以下的内容可能更难理解一些,也需要一些虚拟机字节码方面的知识。

Java 编译后的 .class 文件主要分为以下五个部分(个人理解):
1、魔数和版本号信息
2、常量池信息,包括池中常量的数量和每个常量的描述
3、类信息,包括本身类、父类和接口描述
4、类中声明的属性和方法信息,包括属性和方法的数量和描述,方法中还包括实际执行的虚拟机字节码
5、类本身属性的信息,例如 SourceFile 属性显示类的源文件名称

以下面的 Java 源文件为例,按照上面五个部分说明一下编译后的 .class 文件的格式。

public class PrintString {

private String str = "";

public void setString(String s) {
this.str = s;
}

public String getString() {
return this.str;
}

public void print(){
System.out.println(getString());
}

}

注意:以下使用的数字如无特别说明均为 十六进制。
第一部分占用 8 个字节:

00000000h: CA FE BA BE 00 00 00 32 ; 漱壕...2

前面 4 个字节是魔数,所有 .class 文件都是一样的,为了虚拟机更方便的识别是否为类文件
后面 4 个字节分别是主版本号和次版本号,每个版本 JDK 的编译出的类会有所不同

第二部分占用的字节数取决的常量池的数量和常量的类型:

00000008h: 00 29 07 00 02 01 00 10 74 65 73 74 2F 50 72 69 ; .)......test/Pri
00000018h: 6E 74 53 74 72 69 6E 67 07 00 04 01 00 10 6A 61 ; ntString......ja
00000028h: 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 ; va/lang/Object..
00000038h: 03 73 74 72 01 00 12 4C 6A 61 76 61 2F 6C 61 6E ; .str...Ljava/lan
00000048h: 67 2F 53 74 72 69 6E 67 3B 01 00 06 3C 69 6E 69 ; g/String;...<ini
00000058h: 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 0A ; t>...()V...Code.
00000068h: 00 03 00 0B 0C 00 07 00 08 08 00 0D 01 00 00 09 ; ................
00000078h: 00 01 00 0F 0C 00 05 00 06 01 00 0F 4C 69 6E 65 ; ............Line
00000088h: 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F ; NumberTable...Lo
00000098h: 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 ; calVariableTable
000000a8h: 01 00 04 74 68 69 73 01 00 12 4C 74 65 73 74 2F ; ...this...Ltest/
000000b8h: 50 72 69 6E 74 53 74 72 69 6E 67 3B 01 00 09 73 ; PrintString;...s
000000c8h: 65 74 53 74 72 69 6E 67 01 00 15 28 4C 6A 61 76 ; etString...(Ljav
000000d8h: 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 ; a/lang/String;)V
000000e8h: 01 00 01 73 01 00 09 67 65 74 53 74 72 69 6E 67 ; ...s...getString
000000f8h: 01 00 14 28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F ; ...()Ljava/lang/
00000108h: 53 74 72 69 6E 67 3B 01 00 05 70 72 69 6E 74 09 ; String;...print.
00000118h: 00 1B 00 1D 07 00 1C 01 00 10 6A 61 76 61 2F 6C ; ..........java/l
00000128h: 61 6E 67 2F 53 79 73 74 65 6D 0C 00 1E 00 1F 01 ; ang/System......
00000138h: 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69 6F ; ..out...Ljava/io
00000148h: 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 0A 00 01 ; /PrintStream;...
00000158h: 00 21 0C 00 17 00 18 0A 00 23 00 25 07 00 24 01 ; .!.......#.%..$.
00000168h: 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 ; ..java/io/PrintS
00000178h: 74 72 65 61 6D 0C 00 26 00 15 01 00 07 70 72 69 ; tream..&.....pri
00000188h: 6E 74 6C 6E 01 00 0A 53 6F 75 72 63 65 46 69 6C ; ntln...SourceFil
00000198h: 65 01 00 10 50 72 69 6E 74 53 74 72 69 6E 67 2E ; e...PrintString.
000001a8h: 6A 61 76 61 ; java

前面两个字节 0029 表示常量池的大小 40 个,常量池的内容会被后面所有部分使用到。
第1个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=0002,查看 2 个常量
第2个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16 个字节。内容为 test/PrintString
第3个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=0004,查看 4 号常量
第4个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16 个字节。内容为 java/lang/Object
第5个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0003 说明占用后面的 3 个字节。内容为 str
第6个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18 个字节。内容为 Ljava/lang/String;
第7个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0006 说明占用后面的 6 个字节。内容为 <init>
第8个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0003 说明占用后面的 4 个字节。内容为 ()V
第9个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0004 说明占用后面的 4 个字节。内容为 Code
第10个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0003,name_and_type_index=000B。内容为 java/lang/Object 的 init 方法
第11个常量 0C 是一个 constant_nameAndType 类型,后面的 4 个字节属于它,name_index=0007,descriptor_index=0008,内容为<init>()V
第12个常量 08 是一个 constant_String 类型,后面的 2 个字节属于它,000D 查看13号常量池
第13个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 00 说明占用后面的 0 个字节。内容为空
第14个常量 09 是一个 constant_Fieldref 类型,后面的 4 个字节表示它占用的字节数,class_index=0001 , name_and_type_index =000F。内容为 test/PrintString 的 str
第15个常量 0C 是一个 constant_nameAndType 类型,后面的 4 个字节属于它,name_index=0005,descriptor_index=0006,。内容为 Ljava/lang/String; str
第16个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,000F 说明占用后面的 15 个字节。内容为 LineNumberTable
第17个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18个字节。内容为 LocalVariableTable
第18个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0004 说明占用后面的 4个字节。内容为 this
第19个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18个字节。内容为 Ltest/PrintString;
第20个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0009 说明占用后面的 9个字节。内容为 setString;)
第21个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0015 说明占用后面的 21个字节。内容为 (Ljava/lang/String;)V
第22个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0001 说明占用后面的 1个字节。内容为 s
第23个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0009 说明占用后面的 9个字节。内容为 getString
第24个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0014 说明占用后面的 20个字节。内容为 ()Ljava/lang/String;
第25个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0005 说明占用后面的 20个字节。内容为 print
第26个常量 09 是一个 constant_Fieldref 类型,根据它的定义后面四个字节属于它,class_index=001B 查看27号常量, name_and_type_index = 001D 查看29号常量
第27个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=001C,查看28号常量
第28个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16个字节。内容为 java/lang/System
第29个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=001E 查看30号常量,descriptor_index=001F 查看31号常量
第30个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 03 说明占用后面的 3个字节。内容为 out
第31个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 15 说明占用后面的 21个字节。内容为Ljava/io/PrintStream;
第32个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0001,name_and_type_index=0021
第33个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=0017 查看23号常量,descriptor_index=0018 查看24号常量
第34个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0023 查看35号常量,name_and_type_index=0025 查看37号常量
第35个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=00 24,查看36号常量
第36个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 13 说明占用后面的 19个字节。内容为 java/io/PrintStream
第37个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=0026 查看38号常量,descriptor_index=0015 查看21号常量
第38个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0007 说明占用后面的 7个字节。内容为 println
第39个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,000A 说明占用后面的 10个字节。内容为 SourceFile
第40个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 10 说明占用后面的 16个字节。内容为 PrintString.java

第三部分占用 8 个字节:

000001ach: 00 21 00 01 00 03 00 00 ; .!......

前面 2 个字节 0021 表示类本身的修饰符
中间 2 个字节 0001 表示类本身的常量表索引,内容为 test/PrintString
下面 2 个字节 0003 表示父类的常量表索引,内容为 java/lang/Object
最后 2 个字节 0000 表示类实现的接口数量,此类未实现接口

第四部分是类文件是比较复杂的部分,它分为两个部分:属性区和方法区。
先看属性区:

000001b4h: 00 01 00 02 00 05 00 06 00 00 ; ..........

前面 2 个字节 0001 表示类声明了一个属性
下面 2 个字节 0002 表示后面 2 个字节是属性的描述
下面 2 个字节 0005 表示属性名称的常量池索引,5号常量的信息 str
下面 2 个字节 0006 表示属性描述的常量池索引,6号常量的信息 Ljava/lang/String;
最后 2 个字节 0000 表示属性本身的属性的数量

再看方法区:

000001beh: 00 04 00 01 00 07 00 08 00 01 00 09 00 00 00 3D ; ...............=
000001ceh: 00 02 00 01 00 00 00 0B 2A B7 00 0A 2A 12 0C B5 ; ........*?.*..?
000001deh: 00 0E B1 00 00 00 02 00 10 00 00 00 0E 00 03 00 ; ..?............
000001eeh: 00 00 03 00 04 00 05 00 0A 00 03 00 11 00 00 00 ; ................
000001feh: 0C 00 01 00 00 00 0B 00 12 00 13 00 00
// 此处为第一个方法和第二个方法的交界
00 01 00 ; ................
0000020eh: 14 00 15 00 01 00 09 00 00 00 3E 00 02 00 02 00 ; ..........>.....
0000021eh: 00 00 06 2A 2B B5 00 0E B1 00 00 00 02 00 10 00 ; ...*+?.?......
0000022eh: 00 00 0A 00 02 00 00 00 08 00 05 00 09 00 11 00 ; ................
0000023eh: 00 00 16 00 02 00 00 00 06 00 12 00 13 00 00 00 ; ................
0000024eh: 00 00 06 00 16 00 06 00 01 00 01 00 17 00 18 00 ; ................
0000025eh: 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A ; ....../........*
0000026eh: B4 00 0E B0 00 00 00 02 00 10 00 00 00 06 00 01 ; ?.?...........
0000027eh: 00 00 00 0C 00 11 00 00 00 0C 00 01 00 00 00 05 ; ................
0000028eh: 00 12 00 13 00 00 00 01 00 19 00 08 00 01 00 09 ; ................
0000029eh: 00 00 00 39 00 02 00 01 00 00 00 0B B2 00 1A 2A ; ...9........?.*
000002aeh: B6 00 20 B6 00 22 B1 00 00 00 02 00 10 00 00 00 ; ? ?"?........
000002beh: 0A 00 02 00 00 00 10 00 0A 00 11 00 11 00 00 00 ; ................
000002ceh: 0C 00 01 00 00 00 0B 00 12 00 13 00 00 ; .............

前面 2 个字节 0004 表示类声明四个方法(注意:包含一个隐含的构造方法 init)
下面 2 个字节 0001 表示是一个ACC_PUBLIC 的方法
下面 2 个字节 0007 表示 name_index,表示常量池中第7个常量为<init>,
下面 2 个字节 0008 表示desciptor_index,表示常量池第8个常量为 ()V
下面 2 个字节 0001 表示attribute_count,表示有1个attribute
下面 2 个字节 0009 表示code_attribute,查看第9号常量池为Code
下面4 个字节 0000003D 表示后面的 61 个字节属于这个属性
下面 2 个字节 0002 表示 max_stack 该方法执行的时候操作数栈最大的长度,这里表示操作数栈的长度为1
下面 2 个字节 0001 表示 max_locals 该方法局部变量所需要的空间的长度
下面 4 个字节 00 00 00 0B 表示code_length 即后面的 11 个字节为虚拟机字节码内容
代码的 11 个字节:2A B7 00 0A 2A 12 0C B5 00 0E B1
2A :aload_0 表示将第一个引用类型本地变量推送至栈顶
B7 :invokespecial 调用超类方法,后面的 000A,查看10号常量池 表示调用超类的init方法
2A :aload_0 表示将第一个引用类型本地变量推送至栈顶
12 :ldc 表示将一个常量池压入操作栈,后面的 0C 便是这个操作数,查看第13号常量池,为空
B5 :putfield 将操作栈的值赋给类变量,后面2个字节 000E 代表类变量的常量池索引,为 Ljava/lang/String; str
B1 : return ;返回void
这里我们可以知道类变量的初始化位置。
代码的 11 个字节以后
下面 2 个字节 0000 表示exception_table_length=0;也就是说没有异常处理
下面 2 个字节 0002 表示attributes_count=2,接下来有两个attribute_info 的结构
下面 2 个字节 0010 表示 attribute_name_index,查看10号常量池,为 LineNumberTable
下面 4 个字节 00 00 00 0E 表示attribute_length=14
下面 14 个字节是 LineNumberTable 的属性
下面 2 个字节 0011 表示 attribute_name_index,查看10号常量池,为 LocalVariableTable
下面 4 个字节 00 00 00 0C 表示attribute_length=12
下面 12 个字节是 LocalVariableTable 的属性
以上只是第一个方法的说明,后面的方法不再详细说明。

第五部分通常不是很重要,大概了解一下:

000002dbh: 00 01 00 27 00 00 00 02 00 28 ; ...'.....(

前面 2 个字节 0001 表示接下去有一个 attributes_info 结构,该结构占用 8 个字节
下面 2 个字节 0027 表示属性的名称的常量池索引,39 号常量的信息(属性的名称)为 SourceFile
下面 4 个字节 00000002 表示属性的长度,因为格式固定,所长度为 2
最后 2 个字节 0028 表示 sourcefile_index 的常量池索引, 40 号常量的信息(源文件的名称)为 PrintString.java

以上只是对一个结构简单的类作了一个简要说明,实际情况会复杂很多。详细内容大家可以参考《深入 Java 虚拟机(第二版)》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值