您的位置 首页 java

Java字节码翻译

作为一个大龄程序员,从开始学习java的时候就开始接触 class文件 ,作为一个拿来即用主义的人,仅需要知道怎么跑起程序即可完成所有工作,从来没有想到看什么是 字节码 ,无奈 Java 太内卷了,只能尝试花了一天,去看懂字节码。

啥是class文件

Class文件格式采用一种类似于 C语言 结构体的方式进行数据存储,这种结构中只有两种数据类型:无符号数和表。

无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和 8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF -8编码构成 字符串 值。

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class 文件本质上就是一张表。由于表没有固定长度,所以通常会在其前面加上个数说明。

为了避免与类和类实例等字段的混淆,描述类文件格式的结构的内容被称为项。连续的项是按顺序存储在类文件中,且没有填充或对齐。

class文件结构与每项占用的字节长度

  Class File { 
  u4 magic;                        // 魔数 ,标识为Class文件
  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];          //属性表
}  

javac 对源码进行编译后得到的class文件,打开后类似于乱码的文件,实际上内容是按照class的结构以每一项类似数组按顺序排列。

例如 [u4 magic][u2 minor_version][u2 major_version][…][…][…][…][…][…][…]

参考ClassFile里面的结构,根据字节码文件按照对应的字节数进行翻译。

通过javap -v -p .class文件也能得到字节码翻译后的文件

源代码

javac编译后的class文件

开始痛苦之旅

魔数、主次版本号、常量池计数器

魔数(2字节)

提供标识类文件格式的魔术数字;它的值为0xCAFEBABE。

主版本号(2字节)与次版本号(2字节)

决定class文件的版本号,通常以主版本号.次版本号作为class文件的版本,高版本的字节码文件不能在低版本的虚拟机中运行,Java8对应的版本号为52.0,16 进制 34代表是52。

常量池计数器(2字节)

常量池计数器值为常量池长度 + 1 ,即常量池长度为10 ,那么常量池计数器值为11。该常量池计数器 26即常量池计数器为38。

常量池表

常量池主要存放两大常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值。而符号引用则属于编译原理方面的概念。

常量池中每一项常量都是一个表 ,开始的第一位是一个 u1 类型的标志位 【tag】 来表示常量类型。

 #常量池结构
cp_info { 
  u1 tag; 
  u1 info[]; 
}  

tag代表的含义:

类型

标志(tag)

描述

CONSTANT_utf8_info

1

UTF-8编码的字符串

CONSTANT_Integer_info

3

整型字面量

CONSTANT_Float_info

4

浮点型字面量

CONSTANT_Long_info

5

长整型字面量

CONSTANT_Double_info

6

双精度浮点型字面量

CONSTANT_Class_info

7

类或接口的符号引用

CONSTANT_String_info

8

字符串类型字面量

CONSTANT_Fieldref_info

9

字段的符号引用

CONSTANT_Methodref_info

10

类中方法的符号引用

CONSTANT_InterfaceMethodref_info

11

接口中方法的符号引用

CONSTANT_NameAndType_info

12

字段或方法的符号引用

CONSTANT_MethodHandle_info

15

表示方法句柄

CONSTANT_MethodType_info

16

标志方法类型

CONSTANT_InvokeDynamic_info

18

表示一个动态方法调用点

常量池第一项中0A等于10进制里面的10,根据tag对照表中属于 CONSTANT_Methodref_info结构 一共5个字节。

 CONSTANT_Methodref_info { 
  u1 tag; 
  u2 class_index; 
  u2 name_and_type_index; 
}   

常量池第一项字节码

 {
  tag:CONSTANT_Methodref_info , 
  class_index:6 ,
  name_and_type_index:24
}  
Java字节码翻译

参照javap

第7项

常量池表中第7项中,tag = 01 ,length = 8 ,bytes长度 = 8 , 总长度 1 + 2 + 8 = 11

每项常量池都以这种方式去翻译得出下表,一共37个项,证明常量池表长度 + 1 = 常量池计数器的值。

  #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#25         // simple02/ByteCodeTest.fieldInt:I
   #3 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #28.#29        // java/io/PrintStream.println:(I)V
   #5 = Class              #30            // simple02/ByteCodeTest
   #6 = Class              #31            // java/lang/Object
   #7 = Utf8               fieldInt
   #8 = Utf8               I
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lsimple02/ByteCodeTest;
  #16 = Utf8               method01
  #17 = Utf8               ()I
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               Source File 
  #23 = Utf8               ByteCodeTest.java
  #24 = NameAndType        #9:#10         // "<init>":()V
  #25 = NameAndType        #7:#8          // fieldInt:I
  #26 = Class              #32            // java/lang/System
  #27 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #28 = Class              #35            // java/io/PrintStream
  #29 = NameAndType        #36:#37        // println:(I)V
  #30 = Utf8               simple02/ByteCodeTest
  #31 = Utf8                java /lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (I)V  

此时,正常情况需要停下休息,去治疗一下眼睛了。

访问权限(2字节)

访问标识 用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为 public类型,是否定义为 abstract 类型;如果是类的话,是否被声明为final等。

类索引(2字节)

该索引指向的字面量格式文件的全限定名,如:java/lang/Object

父类索引(2字节)

该索引指向常量池中CONSTANT_Class_info结构。

接口计数器(2字节)

为类实现的接口数量

接口索引表

该索引集合每项指向常量池中CONSTANT_Class_info结构。

访问标识 21 即 ACC_PUBLIC , ACC_SUPER

类索引指向常量池中#5 字面值 = simple02/ByteCodeTest

父类索引指向常量池中#6 字面值 = java/lang/Object

接口计数器=0,所以后面没有接口索引表

字段计数器(2字节)

描述类中有多少字段。

字段索引集合

用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。

字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、 private 或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。

 #字段结构
field_info { 
  u2 access_flags; 
  u2 name_index; 
  u2 descriptor_index; 
  u2 attributes_count; 
  attribute_info attributes[attributes_count]; 
}   

字段计数器 = 1 ,字段表项长度1,字段标识符 = 0x0002 即 ACC_PRIVATE, 字段名 索引=#7字面量= fieldInt,描述=#8 字面量I 即 基本数据类型int, attributes_count = 0

方法计数器(2字节)

表示该类中有多少个方法, 但不包括超类或超接口中的方法数量。

方法表

表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。

如果某个method_info结构的access_flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的 Java虚拟机 指令。

method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法·方法表的结构实际跟字段表是一样的:

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

方法计数器

方发表中第一个方法

根据字节码,方法表中第一个方法有一个属性,属性指向的字面量是Code,其结构

 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 attributes_count;
   attribute_info attributes[attributes_count];
}  

code[]就是属于虚拟机的指令,它由一个字节长度的、代表着某种特定操作含义的数字(称为 操作码 ,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于Java虚拟机采用面向操作数栈而不是 寄存器 的结构,所以大多数的指令都不包含操作数,只有一个操作码。

由于限制了 Java 虚拟机 操作码的长度为一个字节(即0~255),这意味着 指令集 的操作码总数不可能超过256条。

对照字节码指令索引,一个一个查字典方式去标识翻译命令,与javap的文件对比验证是否有误。

 翻译后变成如下代码
 public simple02. ByteCode Test();
    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 fieldInt:I
         9: return
      LineNumberTable:
        line 3: 0
        line 5: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lsimple02/ByteCodeTest;  
Java字节码翻译

得出3个方法,不同颜色标记

终于到最后了。。。。。

attributes_count 、 attributes[]

属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。

此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。

属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但]ava虚拟机运行时会忽略掉它不认识的属性。

名字索引得出属性是属于 SourceFile结构,定长占8个字节,指向常量池中#23,字面值为 ByteCodeTest.java

 #属性结构
attribute_info {
   u2 attribute_name_index;
   u4 attribute_length;
   u1 info[attribute_length];
}


#SourceFile结构
SourceFile_attribute {
   u2 attribute_name_index;
   u4 attribute_length;
   u2 sourcefile_index;
}  

总结

class文件的结构以每一项类似数组按顺序排列。[u4 magic][u2 minor_version][u2 major_version][…][…][…][…][…][…][…]

每项都有对应的结构,根据【java虚拟机规范】文档,好似查字典一样去解析即可。

文档目录

文章来源:智云一二三科技

文章标题:Java字节码翻译

文章地址:https://www.zhihuclub.com/175461.shtml

关于作者: 智云科技

热门文章

网站地图