java字节码文件分析

痛痛快快来分析一下java字节码文件:

根据j2se7 Virtual Machine Specific:http://docs.oracle.com/javase/specs/jvms/se7/html/

  • 源文件:
public class Person {
	private String name;
	public void walk(){
		System.out.println("I am walking.");
	}
}
  • 对应16进制表示:

CA FE BA BE 00 00 00 33 00 21 07 00 02 01 00 06 50 65 72 73 6F 6E 07 00 04 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63  74 01 00 04 6E 61 6D 65 01 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29  56 01 00 04 43 6F 64 65 0A 00 03 00 0B 0C 00 07 00 08 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61  6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 08 4C 50 65 72 73 6F 6E 3B 01 00 04 77 61 6C 6B 09 00 12 00  14 07 00 13 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 0C 00 15 00 16 01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F  69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 08 00 18 01 00 0D 49 20 61 6D 20 77 61 6C 6B 69 6E 67 2E 0A 00 1A 00 1C 07 00 1B 01  00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 0C 00 1D 00 1E 01 00 07 70 72 69 6E 74 6C 6E 01 00 15 28 4C 6A 61  76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0B 50 65 72 73 6F 6E 2E 6A 61 76  61 00 21 00 01 00 03 00 00 00 01 00 02 00 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05  2A B7 00 0A B1 00 00 00 02 00 0C 00 00 00 06 00 01 00 00 00 02 00 0D 00 00 00 0C 00 01 00 00 00 05 00 0E 00 0F 00 00 00 01 00 10  00 08 00 01 00 09 00 00 00 37 00 02 00 01 00 00 00 09 B2 00 11 12 17 B6 00 19 B1 00 00 00 02 00 0C 00 00 00 0A 00 02 00 00 00 06  00 08 00 07 00 0D 00 00 00 0C 00 01 00 00 00 09 00 0E 00 0F 00 00 00 01 00 1F 00 00 00 02 00 20
  • class文件规范格式应为:
ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • 字节码分析:(u1表示1个字节, u2表示2个字节,依次类推...)
CA FE BA BE // 标识该文件为.class文件[u4];
00 00 00 33 // 标识jdk编译版本: 小版本[u2] 大版本[u2], 这里大版本为51, 说明该文件由jdk7编译而来;
00 21 // 标识该class文件的常量池个数[u2], 说明该文件有33个常量项

java7规范中常量池中常量项的类型有(常量项索引从1开始计数, 记住每一种常量类型都有各自的结构):

Constant Type Value
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18
  • 先分析常量池:

第1个常量:

07 00 02
07 // 07 表示该常量为CONSTANT_Class类型的常量,我们看看CONTSTANT_Class类型常量的结构:
CONSTANT_Class:{
   u1: 标志(07)
   u2: 常量池索引,上面这个例子 [00 02] 说明指向的就是第2个常量项,再继续看第2个常量项:即Person
}

第2个常量:

01 00 06 50 65 72 73 6F 6E
01 // 表示该常量为CONSTANT_Utf8类型的常量类型,CONSTANT_Utf8常量类型结构为:
CONSTANT_Uft8:{
   u1: 标志(01)
   u2: 字符串长度, [00 06]表示这个字符串占6个字节
   u[int[u2]]: 表示第三部分的长度由u2值决定,这里是50 65 72 73 6F 6E, 这6个字节对应的ASCII码就是Person, 明白了吧
}

第3个常量:

07 00 04
07 //CONSTANT_Class常量类型,且其字符串指向第4(00 04)个常量,即java/lang/Object

第4个常量:

01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 
01 //CONSTAT_Utf8常量类型,且长度为16[00 10], 紧跟的16个字节为6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74, 就是java/lang/Object

第5个常量:

01 00 04 6E 61 6D 65
01 //CONSTANT_Utf8
00 04 //4个字节长度
6E 61 6D 65 --> name

第6个常量:

01 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 
01 //CONSTANT_Utf8
00 12 //18个字节长度
// 4C 6A ... 67 3B --> Ljava/lang/String;

第7个常量:

01 00 06 3C 69 6E 69 74 3E
01 //CONSTANT_Utf8
00 06 //6个字节长度
3C 69 6E 69 74 3E --> <init>
第8个常量:
01 00 03 28 29 56
01 //CONSTANT_Utf8
00 03 //3个字节
28 29 56 --> ()V

第9个常量:

01 00 04 43 6F 64 65
01 //CONSTANT_Utf8
00 04 //4个字节
43 6F 64 65 --> Code
第10个常量:
0A 00 03 00 0B
0A // CONSTANT_Methodref
CONSTANT_Methodref {
   u1(11) 标志
   u2 指向方法所属类的描述符CONSTANT_Utf8的索引项
   u2 指向方法名称及类型描述符CONSTANT_NameAndType的索引项
}
00 03 //第3个常量:如前面的,为java/lang/Object这个超类
00 0B //第11个常量:根据第11个常量:为<init>方法

第11个常量:

0C 00 07 00 08
0C //CONSTANT_NameAndType
CONSTANT_NameAndType结构:
{
   u1(12) 标志
   u2 指向字段或方法名称的常量项索引
   u2 指向字段或方法描述符的常量索引
}
00 07 //第7个常量:<init>
00 08 //第8个常量: ()V
就是指类实例的初始化方法

第12个常量:

01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 //CONSTANT_Utf8 15字节 LineNumberTable

第13个常量:

01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 //CONSTANT_Utf8 18字节 LocalVariableTable

第14个常量:

01 00 04 74 68 69 73 //CONSTANT_Utf8 4字节 this

第15个常量:

01 00 08 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8 8字节 LPerson;

第16个常量:

01 00 04 77 61 6C 6B //CONSTANT_Utf8 4字节 walk

第17个常量:

09 00 12 00 14
09 //CONSTANT_Fieldref
CONSTANT_Fieldref结构:
{
  u1(9) 标志
  u2 指向字段所属类或接口描述符CONSTANT_Class的索引项
  u2 指向字段描述符CONSTANT_NameAndType的索引项
}
00 12 //第18个常量:即java/lang/System
00 14 //第20个常量:out Ljava/io/PrintStream;

第18个常量:

07 00 13 //CONSTANT_Class 指向第19(00 13)个常量:即java/lang/System

第19个常量:

01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D //CONSTANT_Utf8 16个字节 java/lang/System

第20个常量:

0C 00 15 00 16 //CONSTANT_NameAndType 第21(00 15)个常量:out 第22(00 16)个常量:Ljava/io/PrintStream;

第21个常量:

01 00 03 6F 75 74 //CONSTANT_Utf8 3字节 out

第22个常量:

01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B //CONSTANT_Utf8 21字节 Ljava/io/PrintStream;

第23个常量:

08 00 18 
08 //CONSTANT_String
CONSTANT_String常量类型结构:
{
  u1(08) 标志
  u2 指向字符串字面量的索引
}
00 18 //第24个常量:I am walking.

第24个常量:

01 00 0D 49 20 61 6D 20 77 61 6C 6B 69 6E 67 2E //CONSTANT_Utf8 13(00 0D)字节 I am walking.

第25个常量:

0A 00 1A 00 1C //CONSTANT_Methodref 第26(00 1A)个常量:java/lang/PrintStream 第28(00 1C)个常量:println (Ljava/lang/String;)V

第26个常量:

07 00 1B //CONSTANT_Class 第27(00 1B)个常量: java/lang/PrintStream

第27个常量:

01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D //CONSTANT_Utf8 19(00 13)字节 java/lang/PrintStream

第28个常量:

0C 00 1D 00 1E //CONSTANT_NameAndType 第29(1D)个常量: println 第30(00 1E)个常量: (Ljava/lang/String;)V

第29个常量:

01 00 07 70 72 69 6E 74 6C 6E //CONSTANT_Utf8 7字节 println

第30个常量:

01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 //CONSTANT_Utf8 21(00 15)字节 (Ljava/lang/String;)V

第31个常量:

01 00 0A 53 6F 75 72 63 65 46 69 6C 65 //CONSTANT_Utf8 10(00 0A)字节 SourceFile

第32个常量:

01 00 0B 50 65 72 73 6F 6E 2E 6A 61 76 61 //CONSTANT_Utf8 11(00 0B)字节 Person.java

第33个常量:null

终于结束常量池的解析,进入到其他内容解析:

  • access_flags 表示该类或接口的访问属性

访问属性有:      

ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_FINAL 0x0010 Declared final; no subclasses allowed.
ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction.
ACC_INTERFACE 0x0200 Is an interface, not a class.
ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ANNOTATION 0x2000 Declared as an annotation type.
ACC_ENUM 0x4000 Declared as an enum type.
00 21 // 即ACC_PUBLIC | ACC_SUPER, 确实符合Person类的访问属性,ACC_SUPER指是否允许使用invokespecial指令, jdk1.2后编译出的class文件的该标志均为true
  • this_class
00 01 //指向CONSTANT_Class常量类型的索引,即第1个常量:Person
  • super_class
00 03 //指向CONSTANT_Class常量类型的索引,即第3个常量:java/lang/Object

开始解析接口表:

  • interfaces_count
00 00 //0, 确实我们的Person没有实现接口
  • interfaces(无,因为上面interfaces_count为0,如果有接口,则该属性为 [u2 u2 ..], 表示各接口对应的常量池中的索引项,且类型为CONSTANT_Class)

接口表解析完成。

开始解析字段表:

  • fields_count
00 01 //只有1个字段
  • field_info,这是属性域结构,其结构为:
{
  u2: access_flags //访问标志
  u2: name_index  //对应字段名称,指向常量池索引,类型为CONSTANT_Utf8
  u2: descriptor_index //对应字段描述符,指向常量池索引, 类型为CONSTANT_Utf8
  u2: atrributes_count //字段的属性个数
  attribute_info //属性信息,也具有自己的结构,后面遇到再讲
}
字段的访问标志有这些:
Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; usable only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.
ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ENUM 0x4000 Declared as an element of an enum.
00 02 // 对应ACC_PRIVATE 私有域
00 05 // 字段的名称,看上面第5个常量,即name
00 06 // 字段的描述符,看上面第6个常量,即Ljava/lang/String;
00 00 // 0个属性,即该字段没有额外属性,但是也得说下attribute的结构
//attribute的结构:
{
   u2: attribute_name_index //属性名称,指向常量池索引,类型为CONSTANT_Utf8
   u4: attribute_length //属性长度
   u1: info //具体属性信息,长度就是attribute_length
}
// 如果有多个属性,就按其结构顺序排列下去,这里name字段没有属性,下面就是方法属性等的分析了

字段表解析完毕。

开始解析方法表:

  • methods_count 方法个数
00 02 // 该类有2个方法
  • method_info, 方法信息
{ //有得说method_info结构了,其实和field_info是一样的,只是access_flags取值范围不一样
  u2: access_flags //访问标志
  u2: name_index  //对应方法名称,指向常量池索引,类型为CONSTANT_Utf8
  u2: descriptor_index //对应方法描述符,指向常量池索引, 类型为CONSTANT_Utf8
  u2: atrributes_count //方法的属性个数
  attribute_info //属性信息,同上
}
方法的访问标志表:
Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5).
ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE 0x0040 A bridge method, generated by the compiler.
ACC_VARARGS 0x0080 Declared with variable number of arguments.
ACC_NATIVE 0x0100 Declared native; implemented in a language other than Java.
ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided.
ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.

这里讲一下属性的种类,后面分析得比较多:

Attribute Section Java SE class file
ConstantValue §4.7.2 1.0.2 45.3
Code §4.7.3 1.0.2 45.3
StackMapTable §4.7.4 6 50.0
Exceptions §4.7.5 1.0.2 45.3
InnerClasses §4.7.6 1.1 45.3
EnclosingMethod §4.7.7 5.0 49.0
Synthetic §4.7.8 1.1 45.3
Signature §4.7.9 5.0 49.0
SourceFile §4.7.10 1.0.2 45.3
SourceDebugExtension §4.7.11 5.0 49.0
LineNumberTable §4.7.12 1.0.2 45.3
LocalVariableTable §4.7.13 1.0.2 45.3
LocalVariableTypeTable §4.7.14 5.0 49.0
Deprecated §4.7.15 1.1 45.3
RuntimeVisibleAnnotations §4.7.16 5.0 49.0
RuntimeInvisibleAnnotations §4.7.17 5.0 49.0
RuntimeVisibleParameterAnnotations §4.7.18 5.0 49.0
RuntimeInvisibleParameterAnnotations §4.7.19 5.0 49.0
AnnotationDefault §4.7.20 5.0 49.0
BootstrapMethods §4.7.21 7 51.0

第1个方法解析<init>:

00 01 // 对应ACC_PUBLIC, 即该方法为public
00 07 // 方法名称,看第7个常量,即<init>方法,构造器方法
00 08 // 方法描述符,看第8个常量,即()V
00 01 // <init>方法有1个属性
00 09 // 该属性名称是第9个常量,即Code, 当然我们每个方法确实要有Code才行,哪怕是空的
Code是属性的一种,它也有自己的结构:
{
    u2 attribute_name_index; //指向常量池索引,但其实字节码中并没有其值,固定为Code
    u4 attribute_length;
    u2 max_stack;    //栈最大深度
    u2 max_locals;   //方法局部变量使用的最大空间,单位为Slot, 每个Slot可存放32位的数据类型,除long,double占2个Slot外,其余基本类型都占1个Slot
    u4 code_length;  //方法字节码指令长度,1个指令占1个字节,因为jvm至今只有200来个字节码指令,1个字节可表示256种指令
    u1 code[code_length]; //对应的字节码指令序列,jvm栈是基于栈的指令操作,大多数指令都是单字节指令,但是也有多字节指令,如[invokespecial u2],其后面2个字节指向常量池索引的CONSTANT_Methodref类型的常量
    u2 exception_table_length; //异常表长度,下面是异常表结构
    {   u2 start_pc; 
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length]; //各个异常
    u2 attributes_count; //属性个数
    attribute_info attributes[attributes_count]; //各属性
}
00 00 00 2F attribute_length为37,之所以没有attribute_name_index,其实这里已经固定为Code了,没必要再占2个字节去指向常量池
00 01 //栈最大深度为1
00 01 //局部变量最大空间为1Slot
00 00 00 05 // 指令集长度为5
2A B7 00 0A B1 查看字节码指令集可知: 2A(aload_0,将第一个引用类型(this)本地变量推送至栈顶) B7(invokespecial, 调用超类构造方法,实例初始化方法或私有方法,后2个字节00 0A指向常量池索引,即java/lang/Object.<init>, B1(return))
00 00 // 异常表为0,所以下面就没有异常信息了,直接就attributes_count了
00 02 // 2个属性

解析第1个属性:

00 0C //指向第12个常量,即LineNumberTable, LineNumberTable也有自己的结构:
// LineNumberTable结构
{
    u2 attribute_name_index; //同Code属性一致,固定为LineNumberTable, 所以字节码里也没有为其保留位置
    u4 attribute_length;
    u2 line_number_table_length; //下面是行号表结构
    {   u2 start_pc; // 字节码行号(偏移量)
        u2 line_number; // 源码行号	
    } line_number_table[line_number_table_length];
}
00 00 00 06 // attribute_length属性长度为6, 记住没有attribute_name_index的字节码,否则是多余
00 01 // 有1个行号表
00 00 // 字节码行号:0
00 02 // 源码行号:2

再解析第2个属性:

00 0D // 看第13个常量,即LocalVariableTable局部表量表,LocalVariableTable也是一种属性,其结构为:
// 局部变量表结构:
{
    u2 attribute_name_index; //同理不占字节,固定为LocalVariableTable
    u4 attribute_length;
    u2 local_variable_table_length; //局部变量与当前栈帧的关系结构
    {   u2 start_pc; // 局部变量起始偏移量
        u2 length; //局部变量作用范围长度
        u2 name_index; //局部变量名称,指向常量池索引
        u2 descriptor_index; //局部变量描述符,指向常量池索引
        u2 index; //所在局部变量数组的索引位置, 如果为long或double在index和index+1
    } local_variable_table[local_variable_table_length];
}
00 00 00 0C attrbute_length为12
00 01 // 有一个局部变量与当前栈帧的关系
00 00 // 起始偏移0
00 05 // 作用范围长度为5
00 0E // 名称指向14个常量,即this
00 0F // 描述指向第15个常量,即LPerson;
00 00 // 即为局部变量数组的第0个

解析第2个方法walk:

00 01 //访问属性为public
00 10 //方法名称,第16个常量,即walk
00 08 //方法描述符,第8个常量,即()V
00 01 //属性个数为1

解析该属性:

00 09 //指向第9个常量,即Code
00 00 00 37 //attribute_length为45
00 02 //最大的栈深度为2
00 01 //最大局部变量Slot数为1
00 00 00 09 //代码长度9
B2 00 11 //B2:getstatic 00 11:第17个常量,即System.out的引用放到栈顶
12 17 //12:ldc 讲第23个常量(int, float, 或者String字面量的引用)放到栈顶,即将I am walking.的引用放到栈顶
B6 00 19 //B6:invokevirtual调用实例方法,这里就是println
B1 //B1:return方法返回
00 00 //没有异常表
00 02 //有2个属性

第1个属性LineNumberTable:

00 0C // 第12个常量,即LineNumberTable
00 00 00 0A //属性长度为10
00 02 //有2个行号表
00 00 00 06 00 08 00 07 //2个行号表分别为:0:6 8:7

第2个属性LocalVariableTable:

00 0D //属性类型为LocalVariableTable
00 00 00 0C //属性长度为12
00 01 //有1个局部变量
00 00 //该变量偏移量为0
00 09 //该变量作用范围长度为9
00 0E //该变量名称指向第14个常量,即this
00 0F //该变量描述符指向第15个常量 ,即LPerson;
00 00 //该变量在局部变量数组的第0个位置

到此方法表解析完毕。

下面就是类的属性表了:

00 01 //有1属性
00 1F //属性类型指向第31个常量,即SourceFile
{//SourceFile结构
 u2 attribute_name_index; //固定为SourceFile
 u4 attribute_length; //属性长度
 u2 sourcefile_index; //源文件名称,指向常量池索引
}
00 00 00 02 //属性长度为2
00 20 //源文件名称指向第32个常量,即Person.java

类的属性表解析完毕。

到此整个class文件解析完成,还有一些属性没有体现出来这里,可以自己去试试。

不吝指正。

转载于:https://my.oschina.net/indestiny/blog/194260

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值