一、Java字节码解析

 Java class文件结构

Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,如果是超过8位字节以上空间的数据项,则会按照高位在前的方式(Big-Endian)分割成若干个8位字节进行存储。

格式如下

u4 魔数
u2 次版本号
u2 主版本号
u2 常量池计数器 constant_pool_count
常量池 constant_pool_info constant_pool_count-1
u2 访问标志
u2 类索引
u2 父类索引
u2 接口计数器
u2 接口索引集合
u2 字段计数器
字段表集合 field_info
u2 方法计数器
方法表集合 method_info
u2 属性计数器
属性表集合 attribute_info   

关于u2,u4的理解

主要有u1、u2、u4、u8,分别代表有1个字节、2个字节、4个字节、8个字节

1、魔数(u4)

每个class文件头4个字节代表魔数,它代表了这个文件是否是一个能被虚拟机接受的Class文件
Java class文件的魔数固定为:cafebabe

2、主次版本号(u4)

前两个字节代表次版本号(minorversion),后两个字节代表主版本号(majorversion)

3、常量池

常量池的大小是不固定的,会根据类中常量的多少来确定
首选由一个2个字节16进制的数来定义常量池长度,计算出常量池的10进制是多少,然后-1得出常量池的数量。
常量池的类型主要包含以下14种类型:

CONSTANT_Utf8_info      tag标志位为1,   UTF-8编码的字符串
CONSTANT_Integer_info  tag标志位为3, 整形字面量
CONSTANT_Float_info     tag标志位为4, 浮点型字面量
CONSTANT_Long_info     tag标志位为5, 长整形字面量
CONSTANT_Double_info  tag标志位为6, 双精度字面量
CONSTANT_Class_info    tag标志位为7, 类或接口的符号引用
CONSTANT_String_info    tag标志位为8,字符串类型的字面量
CONSTANT_Fieldref_info  tag标志位为9,  字段的符号引用
CONSTANT_Methodref_info  tag标志位为10,类中方法的符号引用CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用
CONSTANT_Method-Handle_info tag标志位为15,方法句柄
CONSTANT_Method-Type_info tag标志位为16,方法类型
CONSTANT_Invoke-Dynamic_info tag标志位为18,动态方法调用点

4、访问标志

由两个字节标示,用于识别类、接口、枚举、注解等相关类型的访问信息,包含这个class是类还是接口,是不是为public类型,是不是abstract,是否声明了final等,标志位的定义如下:

ACC_PUBLIC   0X0001   public类型
ACC_FINAL      0X0010   声明为final,只有类可以设置
ACC_SUPER    0X0020   使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真ACC_INTERFACE   0X0200   接口
ACC_ABSTRACT    0X0400   abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假
ACC_SYNTHETIC   0X1000   这个类并非由用户代码产生
ACC_ANNOTATION   0X2000   注解
ACC_ENUM   0X4000   枚举

5、类索引、父类索引、接口集合

类索引与父类索引都是由两个字节来标识的。
而接下来的接口计数器的两个字节代表该类实现了几个接口,而每个接口都是由2个字节来标识的

6、字段集合

字段集合由字段计数器和字段集合组成,其中字段计数器由两个字节标识,代表有多少个字段,接下来就是字段集合
字段表的结构如下:

类型                  名称                           数量
u2                     access_flags              1
u2                     name_index               1
u2                     descriptor_index         1
u2                     attribute_count            1
attribute_info    attributes                     attribute_count

字段访问标识包含:

ACC_PUBLIC              0X0001           字段是否public
ACC_PRIVATE            0X0002           字段是否private
ACC_PROTECTED     0X0004           字段是否protected
ACC_STATIC               0X0008           字段是否static
ACC_FINAL                  0X0010          字段是否final
ACC_VOLATILE           0X0040          字段是否volatile
ACC_TRANSIENT        0X0080         字段是否transient
ACC_SYNTHETIC        0X0100         字段是否由编译器自动产生的
ACC_ENUM                  0X0400         字段是否enum

字段描述符包含:

B      基本类型byte
C      基本类型char
D      基本类型double
F      基本类型float
I       基本类型int
J      基本类型long
S     基本类型short
Z     基本类型boolean
V     特殊类型void
L     对象类型,如Ljava/lang/Object

7、方法表集合

用于描述类中的所有方法
方法计数器标识该类中有多少个方法,用2个字节标识,接下来就是方法集合的描述
方法表结构与字段表结构一致

8、属性表集合

class文件、方法表、字段表、属性表都可以有属性表集合,用于描述某些场景专有的信息。
属性计数器标识有多少个属性,用2个字节标识,结下来就是对属性集合的描述。
属性表的结构如下:

类型                  名称                                         数量
u2                     attribute_name_index              1
u4                     attribute_length                        1
u1                     info                                           attribute_length

属性表类型有以下几种:
属性名称                   使用位置                   含义 
Code
  方法表   Java代码编译成的字节码指令
ConstantValue  字段表   final关键字定义的常量值
Deprecated  类,方法表,字段表  被声明为deprecated的方法和字段
Exceptions   方法表   方法抛出的异常
EnclosingMethod   类文件   
仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在外围方法
InnerClasses    类文件   内部类列表
LineNumberTable    Code属性    Java源码的行号与字节码指令的对应关系
LocalVariableTable    Code属性      方法的局部变量描述
StackMapTable    Code属性   
JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
SourceFile   类文件     记录源文件名称
Signature    类,方法表,字段表   
JDK1.5中新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接  口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息SourceDebugExtension   类文件   
JDK1.6中新增的属性,SourceDebugExtension 属性用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用  SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息

Synthetic   类,方法表,字段表     标识方法或字段为编译器自动生成的
LocalVariableTypeTable 类     
JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations   类,方法表,字段表   
JDK1.5新增属性,为动态注解提供支持。RuntimeVisibleAnnotations属性用于注明哪些注          解是运行时(实际上运行时就是进行反射调用)可见
RuntimeInvisibleAnnotations    类,方法表,字段表 
JDK1.5新增的属性,与RuntimeVisibleAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations   方法表   
JDK1.5新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数RuntimeInvisibleParameterAnnotations  方法表 
JDK1.5新增的属性,作用与RuntimeInvisibleAnnotations属性类似,只不过作用对象为方法参数
AnnotationDefault    方法表     JDK1.5新增的属性,用于记录注解类元素的默认值BootstrapMethods    类文件   JDK1.7中新增的属性,用于保invokedynamic指令引用的引导方法限定符

以下为分析的一个DEMO

JAVA代码:
package com.data.marketing.util;
public class TestClass {
    private int a = 0;
    private String string = "ss";
    public TestClass() {}
    public static void test(){}
    public String method(){
        return null;
    }
    public String method2(String str){
        return null;
    }
    public static void main(String[] args) {}


}

字节码:
ca fe ba be 00 00 00 34 00 1e 0a 00 06 00 18 09 00 05 00 19 08 00 1a 09 00 05 00 1b 07 00 1c 07 00 1d 01 00 01 61 01 00 01 49 01 00 06 73 74 72 69 6e 67 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 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 04 74 65 73 74 01 00 06 6d 65 74 68 6f 64 01 00 14 28 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 07 6d 65 74 68 6f 64 32 01 00 26 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 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 0e 54 65 73 74 43 6c 61 73 73 2e 6a 61 76 61 0c 00 0b 00 0c 0c 00 07 00 08 01 00 02 73 73 0c 00 09 00 0a 01 00 21 63 6f 6d 2f 64 61 74 61 2f 6d 61 72 6b 65 74 69 6e 67 2f 75 74 69 6c 2f 54 65 73 74 43 6c 61 73 73 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 00 21 00 05 00 06 00 00 00 02 00 02 00 07 00 08 00 00 00 02 00 09 00 0a 00 00 00 05 00 01 00 0b 00 0c 00 01 00 0d 00 00 00 34 00 02 00 01 00 00 00 10 2a b7 00 01 2a 03 b5 00 02 2a 12 03 b5 00 04 b1 00 00 00 01 00 0e 00 00 00 12 00 04 00 00 00 0c 00 04 00 09 00 09 00 0a 00 0f 00 0d 00 09 00 0f 00 0c 00 01 00 0d 00 00 00 19 00 00 00 00 00 00 00 01 b1 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 0f 00 01 00 10 00 11 00 01 00 0d 00 00 00 1a 00 01 00 01 00 00 00 02 01 b0 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 12 00 01 00 12 00 13 00 01 00 0d 00 00 00 1a 00 01 00 02 00 00 00 02 01 b0 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 16 00 09 00 14 00 15 00 01 00 0d 00 00 00 19 00 00 00 01 00 00 00 01 b1 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 1b 00 01 00 16 00 00 00 02 00 17 

javap -v的结果
public class com.data.marketing.util.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#24         //  java/lang/Object."":()V
   #2  = Fieldref           #5.#25         //  com/data/marketing/util/TestClass.a:I
   #3 = String             #26            // ss
   #4 = Fieldref           #5.#27         // com/data/marketing/util/TestClass.string:Ljava/lang/String;
   #5 = Class              #28            //  com/data/marketing/util/TestClass
  #6 = Class              #29            // java/lang/Object
   #7 = Utf8               a
   #8 = Utf8               I
   #9 = Utf8               string
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8             
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               test
  #16 = Utf8               method
  #17 = Utf8               ()Ljava/lang/String;
  #18 = Utf8               method2
  #19 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #20 = Utf8               main
  #21  = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               SourceFile
  #23  = Utf8               TestClass.java
  #24 = NameAndType        #11:#12        // "":()V
  #25 = NameAndType        #7:#8          // a:I
  #26 = Utf8               ss
  #27 = NameAndType        #9:#10         // string:Ljava/lang/String;
  #28 = Utf8               com/data/marketing/util/TestClass
  #29 = Utf8               java/lang/Object
{
  public  com.data.marketing.util.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1,  args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field a:I
         9:  aload_0
        10: ldc           #3                  // String ss
        12: putfield      #4                  // Field  string:Ljava/lang/String;
        15: return
      LineNumberTable:
        line 12: 0
        line 9: 4
        line 10: 9
        line 13: 15
  public static void test();
    descriptor: ()V
    flags: ACC_PUBLIC,  ACC_STATIC
    Code:
      stack=0, locals=0,  args_size=0
         0: return
      LineNumberTable:
        line 15: 0
  public java.lang.String  method();
    descriptor:  ()Ljava/lang/String;
    flags:  ACC_PUBLIC
    Code:
      stack=1, locals=1,  args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 18: 0
  public java.lang.String  method2(java.lang.String);
    descriptor:  (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2,  args_size=2
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 22: 0
  public static void  main(java.lang.String[]);
    descriptor:  ([Ljava/lang/String;)V
    flags: ACC_PUBLIC,  ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
  line 27: 0
}

字节码解析:
魔数、主次版本号、常量池计数器解析
cafe babe u4 魔数
0000 u2 次版本号 0
0034 u2 主版本号 52
001e u2 常量池计数 30-1 常量池数量转十进制之后-1 
常量池解析,共计29个,列举几个不在详细解析
0a     u1 tag 10 methodref_info
0006 u2 index  #6 java/lang/Object 指向#29
0018 u2 index  #24 <init> (#11) 指向#11:#12 ()V (#12) 

09     u1 tag 9 Fieldref_info 
0005 u2 index #5 com/data/marketing/util/TestClass 指向#28
0019 u2 index #25 a(#7) 指向#7:#8 I(#8) 

08     u1 tag 8 String_info
001a u2 index #26 ss

09     u1 tag 9 Fieldref_info
0005 u2 index #5 com/data/marketing/util/TestClass 指向#28
001b u2 index #27 string 指向#9:#10 Ljava/lang/String;

07     u1 tag 7 Class_info
001c u2 index #28 com/data/marketing/util/TestClass
访问标志、类索引、父类索引、接口计数器、接口索引集合、字段计数器
00 21 访问标志(access_flags) u2   0020|0001 public class
00 05 类索引(this_class) u2  #5 TestClass
00 06 父类索引(super_class) u2  #6  Object
00 00 接口计数器(interfaces_class) u2  0代表没有实现接口  
接口索引集合(interfaces) u2  
00 02 字段计数器(fields_count) u2  2 代表有两个字段
00 02 access_flags u2   0002 字段为private
00 07 字段所在索引 u2 #7 a
00 08 字段描述符索引 u2 #8 I
00 00  属性下字段计数器 u2   0
00 02 access_flags u2   0002 字段为private
00 09 字段所在索引 u2  #9 string
00 0a 字段描述符索引 u2  #10 Ljava/lang/String;
00 00 属性下字段计数器 u2   0 
方法计数器、方法表集合(列举一个方法)
Code属性表参考:https://segmentfault.com/a/1190000008722128
00 05 方法计数器(methods_count) u2 5 代表有5个方法
00 01 访问标志(access_flags) u2   0001 代表public  
00 0b 方法名称的索引(name_index) u2  #11 <init>  
00 0c 方法描述符索引(description_index) u2  #12 ()V 代表返回值是void
00 01 属性计数器(attribute_count) u2   1 代表有一个属性
00 0d 属性名称索引(attribute_name_index) u2 #13 Code 遇见了Code去Code属性表
00 00 00 34  属性长度(attribute_length) u4 52 长度为52个字节
00 02 操作栈深度(max_stack) u2   2 栈深为2
00 01  局部变量表所需的存储空间(max_locals) u2   1 代表局部变量所需存储空间为1 00 00 00 10  字节码指令长度(code_length) u4 16  
2aaload_0    将第一个引用类型本地变量 
b7 invokespecial  调用超类构造方法,实例初始化方法,私有方法
00 nop     什么都不做
01 aconst_null  将null推送至栈顶 
2a aload_0  将第一个引用类型本地变量
03 iconst_0  将int型0推送至栈顶
b5 putfield  为指定的类的实例域赋值 
00 nop   什么都不做
02 iconst_m1  将int型-1推送至栈顶
2a aload_0  将第一个引用类型本地变量
12 ldc   将int, float或String型常量值从常量池中推送至栈顶
03 iconst_0   将int型0推送至栈顶 
b5 putfield   为指定的类的实例域赋值
00 nop  什么都不做
04 iconst_1   将int型1推送至栈顶
b1  return           从当前方法返回void
00 00 异常表长度(execption_table_length) u2   0 长度为0,所以其异常表没有
00 01 属性表长度(attribute_count) u2   1 代表这个Code里面有一个其他属性表
00 0e  属性名称索引(attribute_name_index) u2  #14 LineNumberTable 遇见了LineNumberTable去LineNumberTable属性表
00 00 00 12  属性长度(attribute_length) u4  18  
00 04  行号表长度(line_number_table_length) u2 4  
00 00 00 0c  行号表(line_number_info) u4 两个  0:12 前两个为字节码行号,后两个为源码行号
00 04 00 09  行号表(line_number_info) u4 两个  4:9 前两个为字节码行号,后两个为源码行号
00 09 00 0a  行号表(line_number_info) u4 两个  9:10 前两个为字节码行号,后两个为源码行号
00 0f 00 0d 行号表(line_number_info) u4 两个 15:13 前两个为字节码行号,后两个为源码行号


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容