深入理解JVM-java字节码文件结构剖析(1)

public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

javap -verbose MyTest1
警告: 二进制文件MyTest1包含jvm.bytecode.MyTest1
Classfile /Users/luozhiyun/Documents/work/jvm_lecture/target/classes/jvm/bytecode/MyTest1.class
  Last modified Mar 14, 2019; size 471 bytes
  MD5 checksum b2dc69fae4f63b54509ddc1a9210e9c3
  Compiled from "MyTest1.java"
public class 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         // jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // 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               Ljvm/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               jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public 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 6: 0
        line 8: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Ljvm/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 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/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 15: 0
        line 16: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Ljvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

对应的16进制
cafe babe
魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:OxCAFEBABE

0000 0034
魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。这里的版本号为00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为52。所以,该文件的版本号为:1.8.0

0018
表示一共有24个常量 

0a 代表值为10 Methodref_info 这个常量 
00 04 00 14 代表4 和 20   即上面反编译的结果 #4.#20 

09 表示Fieldref  
0003 0015  表示3 和 21 即上面反编译的结果 #3.#21

07 表示Class 
00 16 代表 #22

07 表示Class 
00 17 代表 #23

01 表示utf8
00 01 长度为1
61 代表a

01 表示utf8
00 01 长度为1
49 代表I

0c  表示NameAndType
00 07 表示指向该字段或方法名称常量项的索引  #7:
00 08 表示指向该字段或方法描述符常量项的索引  #8

0c 
00 05 表示指向该字段或方法名称常量项的索引  #5
00 06 表示指向该字段或方法描述符常量项的索引  #6

Access flag
00 21 表示ACC_PUBLIC 与ACC_SUPER取的并集

This Class Name
00 03 表示的一个索引指的是常量池第三个常量

This Super Name
00 04 表示的一个索引指的是常量池第四个常量

Interfaces  
00 00 表示没有实现接口

Fields
00 01 表示属性的数量,表示有一个字段
00 02	 access_flag 表示private
00 05 name_index 名字的索引
00 06 descriptor_index 描述符的索引
00 00 表示attributes_count为0,也就没有attributes_info

Method
00 03 代表有三个方法,包含了自动生成的默认构造器
00 01 代表是一个public的方法
00 07 代表是name_index  <init>
00 08 代表是descriptor_index. ()V
00 01 代表是attribute_count,有一个属性
method attribute
00 09 代表属性名的索引attribute_name_index  code
00 00 00 38 attribute_length  56个字节长度
00 02 max_stack
00 01 max_local 局部变量最大值
00 00 00 0a code_length 方法的代码长度 10个字节,代表这个方法所真正运行的字节码
2a b7 00 01 2a 04 b5 00 02 b1
实际上就是对应着下面的助记符
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

2a -> aload_0
b7 -> invokespecial
04 -> aload_0
b5 -> putfield (为成员变量设置,通过这个变量我们可以知道,成员变量其实是在构造器中赋值的)
b1 -> return

exception
00 00

attributes
00 02 attribute_count; 表示有两个属性
00 0a 属性的索引 #10  LineNumberTable
00 00 00 0a attribute_length 10个字节
00 02 00 00 00 06 00 04 00 08 
00 02 表示两两映射  
00 00  -> 00 06 表示偏移量为0映射到行号为6
00 04  -> 00 08 表示偏移量为4映射到行号为8

00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字节长度
00 01 00 00 00 0a 00 0c 00 0d 00 00
00 01 局部变量的个数
00 00	局部变量开始的位置
00 0a 局部变量结束的位置
00 0c 局部变量索引   #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用来做校验检查的

第二个方法
00 01 访问修饰符 代表public
00 0e 方法的名字索引  getA
00 0f	方法的描述符的索引 ()I
00 01 代表是attribute_count,有一个属性
00 09 代表属性名的索引attribute_name_index  code
00 00 00 2f 属性的长度47
00 01 max_stack
00 01 max_local 局部变量的数量是1 
00 00 00 05 code_length 方法的代码长度 5个字节
2a b4 00 02 ac 方法的执行体
2a -> aload_0
b4 -> getField. 
00 02 表示常量池中的第二个常量 #2
ac -> ireturn 表示返回一个整型

exception
00 00

attributes
00 02 attribute_count; 表示有两个属性
00 0a 索引指向第10个常量 LineNumberTable
00 00 00 06 attribute_length 6个字节
00 01 00 00 00 0b 表示只有一行, 偏移量为零对应行号8
00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字节长度 12个

00 01 00 00 00 05 00 0c 00 0d 00 00
00 01 局部变量的个数
00 00	局部变量开始的位置
00 05 局部变量结束的位置
00 0c 局部变量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用来做校验检查的

第三个方法
00 01 表示public             
00 10 表示索引#16  setA
00 11	表示索引#17  (I)V
00 01 代表是attribute_count,有一个属性
00 09 代表属性名的索引attribute_name_index  code
00 00 00 3e code_length 方法的代码长度 62个字节
00 02 max_stack
00 02 max_local
00 00 00 06 code_length 方法的代码长度 6个字节
2a 1b b5 00 02 b1
2a -> aload_0
1b -> iload_1
b5 -> putfield
00 02 表示第二常量
b1 -> return 

attributes
00 02 attribute_count; 表示有两个属性
00 0a 索引指向第10个常量 LineNumberTable
00 00 00 0a attribute_length 10个字节
00 02 00 00 00 0f 00 05 00 10
00 02 表示有两个对应关系
00 00 00 0f 偏移量为0的对应的是12行
00 05 00 10 偏移量为5的对应的是16行
00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 16 表示LocalVariableTable所占的字节长度 22个
00 02 00 00 00 06 00 0c 00 0d 00 00 00 00 00 06 00 05 00 06 00 01
00 02 局部变量的个数
00 00	局部变量开始的位置
00 06 局部变量结束的位置
00 0c 局部变量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00	局部变量开始的位置
00 06 局部变量结束的位置
00 05 局部变量索引的位置  #5 a
00 06 索引#6 I

Attributes
00 01 
00 12 索引 #18  sourceFile
00 00 00 02 源文件的长度
00 13 索引#19  MyTest1.java

cafe babe 0000 0034 0018 0a00 0400 1409
0003 0015 0700 1607 0017 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 164c 6a76 6d2f 6279
7465 636f 6465 2f4d 7954 6573 7431 3b01
0004 6765 7441 0100 0328 2949 0100 0473
6574 4101 0004 2849 2956 0100 0a53 6f75
7263 6546 696c 6501 000c 4d79 5465 7374
312e 6a61 7661 0c00 0700 080c 0005 0006
0100 146a 766d 2f62 7974 6563 6f64 652f
4d79 5465 7374 3101 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0021 0003 0004
0000 0001 0002 0005 0006 0000 0003 0001
0007 0008 0001 0009 0000 0038 0002 0001
0000 000a 2ab7 0001 2a04 b500 02b1 0000
0002 000a 0000 000a 0002 0000 0006 0004
0008 000b 0000 000c 0001 0000 000a 000c
000d 0000 0001 000e 000f 0001 0009 0000
002f 0001 0001 0000 0005 2ab4 0002 ac00
0000 0200 0a00 0000 0600 0100 0000 0b00
0b00 0000 0c00 0100 0000 0500 0c00 0d00
0000 0100 1000 1100 0100 0900 0000 3e00
0200 0200 0000 062a 1bb5 0002 b100 0000
0200 0a00 0000 0a00 0200 0000 0f00 0500
1000 0b00 0000 1600 0200 0000 0600 0c00
0d00 0000 0000 0600 0500 0600 0100 0100
1200 0000 0200 13

  1. 使用javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、 常量池、类的构造方法、类中的方法信息、类变量与成员变量等信息。

  2. 魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:OxCAFEBABE

  3. 魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。这里的版本号为00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为52。所以,该文件的版本号为:1.8.0

  4. 常量池(content pool):紧接着主版本号之后的就是常量池入口。一个java类中定义的很多信息都是由常量池来维护和描述的。可以将常量池看坐是Class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。

  5. 常量池的总体结构:java类所对应的常量池主要由常量池数量与常量池数组这两部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。jvm在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数 -1 (其中0暂时不适用),目的是满足某些常量池索引值的数据在特定情况下需要表达「不引用任何一个常量池」的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它部位与常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始。

  6. 在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;

  7. 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;

  8. 用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述,参数列表按照参数的严格顺序放在一组()之内,如方法:String getRealnamByIdANdNickName(int id,String name)的描述符为:(I,Ljava/lang/String;)Ljava/lang/String;

Java字节码整体结构

  • 4个字节 Magic Number 魔数值为OxCAFEBABE
  • 2+2个字节 Version 前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。1.1(45),1.2(46),1.3(47),1.4(48),1.5(49),1.6(50),1.7(51)
  • 2+ n个字节 Constant Pool 包括字符串常量、数值常量等
  • 2个字节 Access Flags 访问标志(public class 、private class 等)
  • 2个字节 This Class Name
  • 2个字节 Super Class Name
  • 2+n个字节 Interfaces
  • 2+n个字节 Fields 当前这个类的成员变量的信息
  • 2+n个字节 Methods
  • 2+n个字节 Attributes 当前这个类的附加的属性

Class字节码中有两种数据类型
* 字节数据直接量:这事基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据
* 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。

ClssFile{
	u4 				magic;
	u2				minor_version;
	u2				major_version;
	u2				constant_pool_count;
	cp_info			contant_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 				method_count;
	method_info 	methos[method_count];
	u2				attributes_count;
	attribute_info	attributes[attributes_count];
}

Access_Flag访问标志

访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final。通过上面的源代码,我们知道该文件是类并且是public。

字段表集合
字段表用于描述类和接口中声明的变量。这里的字段包含了类基表变量以及实例变量,但是不包括方法内部声明的局部变量。

field_info{
	u2 				access_flags; 0002
	u2 				name_index;  0005
	u2				descriptor_index; 0006
	u2				attributes_count;	0000
	attribute_info	attributes[attributes_count];
}

方法表

前三个字段和field_info一样

method_info{
	u2					access_flags;
	u2 					name_index;
	u2					descriptor_index;
	u2					attributes_count;
	attribute_info		attributes[attributes_count];
}

方法的属性结构
方法中的每个属性都是一个attribute_info结构

attribute_info{
	u2 	attribute_name_index;
	u4	attribute_length;
	u1	info[attribute_length];
}
* jvm预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
* 不同的attribute通过attribute_name_index来区分

Code结构

Code attribute的作用是保存该方法的结构,如所对应的字节码

Code_attribute{
	u2	attribute_name_index;
	u4	attribute_length;
	u2	max_stack;
	u2	max_locals;
	u4	code_length;
	u1	code[code_length];
	u2	exception_table_length;
	{
		u2	start_pc;
		u2	end_pc;
		u2	handler_pc;
		u2	catch_type;
	}	exception_table[exception_table_length];
	u2	attribute_count;
	attribute_info attributes[attribute_count];
}
* 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代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数

lineNumberTable_attribute{
	u2 attribute_name_index;
	u4	attribute_length;
	u2	line_number_table_length;
	{
		u2 start_pc
		u2	line_number;
	}line_number_table[line_number_table_length];
}

上面的信息可以用jClasslib这样的一个工具来查看

原文地址:https://www.cnblogs.com/luozhiyun/p/10546608.html