Class文件结构分析
一直想写一个字节码的解析的博客出来,就从最简单的开始吧 我也是现学现卖
public class Student { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } }
对于这个Simple Model 在我的机器上编译后的字节码如下
cafe babe 0000 0033 0015 0a00 0400 1109 0003 0012 0700 1307 0014 0100 0269 6401 0001 4901 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0005 6765 7449 6401 0003 2829 4901 0005 7365 7449 6401 0004 2849 2956 0100 0a53 6f75 7263 6546 696c 6501 000c 5374 7564 656e 742e 6a61 7661 0c00 0700 080c 0005 0006 0100 0753 7475 6465 6e74 0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 7400 2100 0300 0400 0000 0100 0200 0500 0600 0000 0300 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0001 0001 000b 000c 0001 0009 0000 001d 0001 0001 0000 0005 2ab4 0002 ac00 0000 0100 0a00 0000 0600 0100 0000 0500 0100 0d00 0e00 0100 0900 0000 2200 0200 0200 0000 062a 1bb5 0002 b100 0000 0100 0a00 0000 0a00 0200 0000 0900 0500 0a00 0100 0f00 0000 0200 10
直接一看 应该觉得比较复杂 但是其实我们是可以把它给分析一下的
对于下面的计算有不理解其实是缺少对字段表 方法表 属性表的结构有了解 还有10进制与16进制的转换
//最开始的4个字节是class文件的魔数,其实很多其它格式文件也是靠魔数来识别的
cafe babe
//表明这个是JDK_1.7 编译而成的
0000 0033
//接下来就是常量池 也可以理解为Class文件的资源仓库
//由于常量池的大小是不固定的 所以需要计数 0x15=21(DEC) 即有21项 从1-21
//为什么索引从1开始 是把第0项空出来有时候要表达 “不应用任何一个常量池项目”
0015
//常量池主要是 字面量和符号引用
//接下来需要一个常量项的结构表
//#1 0a是10 意思是 CONSTANT_Methodref_info 这种项目类型tag(u1) index(u2) index(u2) 指向4和17
0a 0004 0011
//#2 //09是9 是字段的符号引用 这种项目类型tag(u1) index(u2) index(u2) 指向4和17
09 0003 0012
#3//07 是类或符号的接口引用 这种项目类型tag(u1) index(u2) 指向19
07 0013
//#4//07 是类或符号的接口引用 这种项目类型tag(u1) index(u2) 指向20
07 0014
//#5//01是UTF-8编码的字符串 这种项目类型tag(u1) 字节数index(u2) 字节数据bytes(u1) 6764其实就是 id \u0069\u0064
//可以检测一下
public static void main(String[] args) {
System.out.println("\u0069\u0064");
}
-----输出id
01 0002 69 64
#6//01是UTF-8编码的字符串 长度为一 49 即是 I i-20=49(0x)
01 0001 49
//#7 = Utf8 <init>
01 0006 3c 69 6e 69 74 3e
//#8 = Utf8 ()V
01 0003 28 29 56
//#9 = Utf8 Code
01 0004 43 6f64 65
#10 = Utf8 LineNumberTable
01 000f 4c69 6e65 4e75 6d62 6572 5461 626c 65
#11 = Utf8 getId
01 0005 6765 7449 64
#12 = Utf8 ()I
01 0003 2829 49
#13 = Utf8 setId
01 0005 73 65 74 49 64
#14 = Utf8 (I)V
01 0004 28 49 29 56
#15 = Utf8 SourceFile
01 000a 53 6f75 7263 6546 696c 65
#16 = Utf8 Student.java
01 000c 5374 7564 656e 742e 6a61 7661
#17 = NameAndType #7:#8 // "<init>":()V
0c 0007 0008
#18 = NameAndType #5:#6 // id:I
0c 0005 0006
#19 = Utf8 Student
01 0007 53 7475 6465 6e74
#20 = Utf8 java/lang/Object
01 0010 6a 6176 612f 6c61 6e67 2f4f 626a 6563 74
//访问标志 0x0021=0x0001|0x0020 这个计算是对有某个标志的进行或运算
0021
//类索引 父类索引 接口索引 对于类索引 由3指向第三个常量表项 3又指向 19 其实就是 Student 4是java/lang/Object 所有类全部继承Object
0003 0004 0000
//类文件接下来就是字段表了 首先是字段表的计数器fields_count为1
0001
//结构为 u2 u2 u2 u2 attribute_info 代表访问控制 索引 描述索引(变量数据类型) 对于这个程序的变量比较简单 后面的属性表为0
//private int id 的意思
0002 0005 0006 0000
//到了方法表了 方法计数3 一个默认构造 两个方法
0003
//方法表结构 u2 u2 u2 u2 attribute_info 代表 访问控制 name索引 数据类型索引 属性表数量 属性表内容 有一个属性表 0009指向09 Code
0001 0007 0008 0001 0009
//属性表较其它表而言比较松散 u2 u4 u1 代表 名字索引 内容长度 其他信息 本例长度为29 向后取29个长度即可 其它信息没有置空
0000001d
//Code属性 u2 u4 u2 u2 u4 u1 u2 exception_info u2 attribute_info 代表 名称索引指向Code常量 u4长度 从下个u2开始
//stack=1, locals=1 code_length=5, 接下来就是指令 2a代表aload_0 b7代表invokespecial 具体指令意义就不写了0001是invokespecial的参数 代表 #1 = Methodref #4.#17 b1 代表return 返回此方法
0001 0001 00000005 2a b7 00 01 b1
//exception_table_length 为0 没有异常要处理
0000
//attributes_count 为1 指向#10 = Utf8 LineNumberTable
0001 000a
//attribute_length u4类型6 接下来是 line_number_table_length u2 值为1 然后是两个u2 代表字节码行号和Java源码行号
00000006 0001 0000 0001
//下一个方法 getId
0001 000b 000c 0001 0009
//长度为29
0000001d
//stack=1, locals=1 code_length=5 b4指令 getfield 获取字段放入栈顶 0002代表 #2 = Fieldref #3.#18 // Student.id:I ac代表itrturn 从当前方法返回int型数据
0001 0001 00000005 2a b4 00 02 ac
//没有异常
0000
//attributes_count 为1 指向#10 = Utf8 LineNumberTable
0001 000a
//attribute_length u4类型6 接下来是 line_number_table_length u2 值为1 然后是两个u2 代表字节码行号和Java源码行号
00000006 0001 0000 0005
//下一个方法 setId
0001 000d 000e 0001 0009
//长度
00000022
//stack=2, locals=2 code_length=5 2a aload_0第一个引用变量送入栈顶 1b指令 iload_1 第二个int型 putfield b1代表void 从当前方法
0002 0002 00000006 2a 1b b5 00 02 b1
//没有异常
0000
//attributes_count 为1 指向#10 = Utf8 LineNumberTable
0001 000a
//attribute_length u4类型10 接下来是 line_number_table_length u2 值为2 然后是两个u2 代表字节码行号和Java源码行号
//line 0: 9 line 5: 10
0000000a 0002 0000 0009 0005 000a
//以上是全部方法
//接下来是其他属性长度
0001
//SourceFile属性 u2 u4 u2 代表15指向SourceFile长度为2指向16即是 Student.java
000f 00000002 0010
结合javap生成的分析 对比查看: javap -v -p Student
Classfile /D:/temp/Student.class Last modified 2014-4-4; size 335 bytes MD5 checksum 7564cc9cb5db7e7025be6064215ccd6d Compiled from "Student.java" public class Student SourceFile: "Student.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#17 // java/lang/Object."<init>":()V #2 = Fieldref #3.#18 // Student.id:I #3 = Class #19 // Student #4 = Class #20 // java/lang/Object #5 = Utf8 id #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 getId #12 = Utf8 ()I #13 = Utf8 setId #14 = Utf8 (I)V #15 = Utf8 SourceFile #16 = Utf8 Student.java #17 = NameAndType #7:#8 // "<init>":()V #18 = NameAndType #5:#6 // id:I #19 = Utf8 Student #20 = Utf8 java/lang/Object { private int id; flags: ACC_PRIVATE public Student(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public int getId(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field id:I 4: ireturn LineNumberTable: line 5: 0 public void setId(int); flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #2 // Field id:I 5: return LineNumberTable: line 9: 0 line 10: 5 }
可以发现其实就一样的结果 这样我们就对class文件有些认识了
附表
Class文件总体架构
常量池项头表明常量池项的类型
常量池项类型 | 值 | 说明 |
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int型常量 |
CONSTANT_Float | 4 | Float型常量 |
CONSTANT_Long | 5 | Long型常量 |
CONSTANT_Double | 6 | double型常量 |
CONSTANT_Class | 7 | 对一个class的符号引用 |
CONSTANT_String | 8 | String型常量 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类方法的符号引用 |
CONSTANT_InterfaceMedthodref | 11 | 对一个接口方法的符号引用 |
CONSTANT_NameAndType | 12 | 对名称和类型的符号引用 |
如有错误 欢迎指正。。。