Java字节码详解系列之二--解析字节码

1、javap查看字节码内容

上文介绍了字节码的结构,本文主要通过一个简单的例子说明class字节码的每一个字段。

package com.zcm.test;

import java.io.Serializable;

public class SourceTest implements Serializable{
    public int a=3;
    static Integer b=6;
    String s="Hello World!";

    public static void main(String[] args){
        SourceTest test=new SourceTest();
        test.a=9;
        b=8;
    }
    private String test(){
        return s;
    }
}

使用javac -d SourceTest.java 编译该文件,生成SourceTest.class。

使用javap -verbose SourceTest 就会分析出该class的字节码内容

Classfile /Users/zcm/Downloads/com/zcm/test/SourceTest.class
  Last modified 2018-4-2; size 694 bytes
  MD5 checksum 5874dd9682f495b853d6ccb7b8af652b
  Compiled from "SourceTest.java"
public class com.zcm.test.SourceTest implements java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#28         // java/lang/Object."":()V
   #2 = Fieldref           #5.#29         // com/zcm/test/SourceTest.a:I
   #3 = String             #30            // Hello World!
   #4 = Fieldref           #5.#31         // com/zcm/test/SourceTest.s:Ljava/lang/String;
   #5 = Class              #32            // com/zcm/test/SourceTest
   #6 = Methodref          #5.#28         // com/zcm/test/SourceTest."":()V
   #7 = Methodref          #33.#34        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #8 = Fieldref           #5.#35         // com/zcm/test/SourceTest.b:Ljava/lang/Integer;
   #9 = Class              #36            // java/lang/Object
  #10 = Class              #37            // java/io/Serializable
  #11 = Utf8               a
  #12 = Utf8               I
  #13 = Utf8               b
  #14 = Utf8               Ljava/lang/Integer;
  #15 = Utf8               s
  #16 = Utf8               Ljava/lang/String;
  #17 = Utf8               
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               test
  #24 = Utf8               ()Ljava/lang/String;
  #25 = Utf8               
  #26 = Utf8               SourceFile
  #27 = Utf8               SourceTest.java
  #28 = NameAndType        #17:#18        // "":()V
  #29 = NameAndType        #11:#12        // a:I
  #30 = Utf8               Hello World!
  #31 = NameAndType        #15:#16        // s:Ljava/lang/String;
  #32 = Utf8               com/zcm/test/SourceTest
  #33 = Class              #38            // java/lang/Integer
  #34 = NameAndType        #39:#40        // valueOf:(I)Ljava/lang/Integer;
  #35 = NameAndType        #13:#14        // b:Ljava/lang/Integer;
  #36 = Utf8               java/lang/Object
  #37 = Utf8               java/io/Serializable
  #38 = Utf8               java/lang/Integer
  #39 = Utf8               valueOf
  #40 = Utf8               (I)Ljava/lang/Integer;
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  static java.lang.Integer b;
    descriptor: Ljava/lang/Integer;
    flags: ACC_STATIC

  java.lang.String s;
    descriptor: Ljava/lang/String;
    flags:

  public com.zcm.test.SourceTest();
    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_3
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: ldc           #3                  // String Hello World!
        12: putfield      #4                  // Field s:Ljava/lang/String;
        15: return
      LineNumberTable:
        line 5: 0
        line 6: 4
        line 8: 9

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #5                  // class com/zcm/test/SourceTest
         3: dup
         4: invokespecial #6                  // Method "":()V
         7: astore_1
         8: aload_1
         9: bipush        9
        11: putfield      #2                  // Field a:I
        14: bipush        8
        16: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: putstatic     #8                  // Field b:Ljava/lang/Integer;
        22: return
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 14
        line 14: 22

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        6
         2: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #8                  // Field b:Ljava/lang/Integer;
         8: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "SourceTest.java"

通过javap -verbose命令可以看到字节码所包含的信息。

2、查看字节码二进制

在Mac系统下可以通过vim查看class文件,键入:%!xxd使用十六进制查看

Java字节码详解系列之二--解析字节码_第1张图片

3、class文件结构详解
魔数

所有的.class字节码文件的开始4个字节都是魔数,并且其值一定是0xCAFEBABE。如果该值不是0xCAFEBABE,则JVM拒绝加载该文件。


版本号

魔数之后的4个字节为版本信息,前2个字节表示major version,即主版本号;后2个字节表示minor version,即次版本号。

这里的版本号值为0x00000034对应的十进制数是52,这里我用Java1.8编译出来的,其值是52。如果class文件的版本号超出了虚拟机支持的最高版本号,JVM将抛出java.lang.UnsupportedClassVersionError异常。


常量池

Java类中的绝大多数信息都由常量池描述,常量池主要由常量池数量和常量池数组俩部分组成。常量池数量紧跟在次版本号的后面,占2个字节,class字节码的排列非常紧凑。而常量池数组紧跟在常量池数量之后。

每一个常量池元素都由两部分组成:tag和数组内容。使用结构化的方式来描述常量池数组的编排,描述如下:

tag1元素内容tag2元素内容tag3元素内容tag4元素内容....

常量池一共定义了11中常量,如下表:

JVM常量表
编号   常量池元素名称 tag位标识 含义
1 CONSTANT_Utf8_info 1 UTF-8编码的字符串
2 CONSTANT_Integer_info    3 整型字面量
3 CONSTANT_Float_info   4 浮点型字面量
4 CONSTANT_Long_info 5 长整型字面量
5 CONSTANT_Double_info    6 双精度字面量
6 CONSTANT_Class_info 7 类或接口的符号引用
7 CONSTANT_String_info  8 字符串类型的字面量
8 CONSTANT_Fieldref_info 9

字段的符号引用

9 CONSTANT_Methodref_info    10 类中方法的符号引用
10 CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
11 CONSTANT_NameAndType_info  12 字段和方法的名称以及类型的符号引用

根据前面介绍的字节码格式,紧接着的第9,10字节是常量池的数量,后面是常量池数组内容。

下面给出JVM所定义的常量池中每一种元素的具体结构。

CONSTANT_Utf8_info

类型

结构

说明

u1

tag

CONSTANT_Utf8 (1)

u2

length

bytes所代表的字符串的长度

u1

bytes[length]

字符串的byte数据

CONSTANT_Integer_info

类型

结构

说明

u1

tag

CONSTANT_Integer (3)

u4

bytes

整型常量值

CONSTANT_Float_info

类型

结构

说明

u1

tag

CONSTANT_Float(4)

u4

bytes

单精度浮点型常量值

CONSTANT_Long_info

类型

结构

说明

u1

tag

CONSTANT_Long (5)

u4

high_bytes

长整型的高四位值

u4

low_bytes

长整型的低四位值

CONSTANT_Double_info

类型

结构

说明

u1

tag

CONSTANT_Double(6)

u4

high_bytes

双精度浮点的高四位值

u4

low_bytes

双精度浮点的低四位值

CONSTANT_Class_info format

类型

结构

说明

u1

tag

CONSTANT_Class (7)

u2

name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。

CONSTANT_String_info

类型

结构

说明

u1

tag

CONSTANT_String(8)

u2

string_index

constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。

CONSTANT_Fieldref_info

类型

结构

说明

u1

tag

CONSTANT_Fieldref(9)

u2

class_index

constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。

u2

name_and_type_index

constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。

CONSTANT_Methodref_info

类型

结构

说明

u1

tag

CONSTANT_Methodref(10)

u2

class_index

constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。

u2

name_and_type_index

constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。

CONSTANT_InterfaceMethodref_info

类型

结构

说明

u1

tag

CONSTANT_InterfaceMethodref(11)

u2

class_index

constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。

u2

name_and_type_index

constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。

CONSTANT_NameAndType_info

类型

结构

说明

u1

tag

CONSTANT_NameAndType (12)

u2

name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。

u2

descriptor_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的描述符

接着上面的分析,后面跟在常量池数量后面的就是常量池数组,按照上面JVM常量表的顺序排列。这里就不一一描述了。

访问标识 access_flags

常量池后面紧跟的就是access_flags结构,该结构占2个字节,类型是u2。代表访问标志位,用于标注类或接口的访问信息。access_flags的可选值如下: 

标志名称 含义
ACC_PUBLIC 0×0001 是否为pubilc类型
ACC_FINAL 0×0010 是否为final类型,只有类可设置
ACC_SUPER 0×0020 用于兼容早期编译器,新编译器都设置该标记,以在使用invokespecial指令时对子类方法做特定处理。
ACC_INTERFACE 0×0200 标识为接口。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM
ACC_ABSTRACT 0×0400 抽象类,无法实例化。不可与ACC_FINAL同时设置。
ACC_SYNTHETIC 0×1000 synthetic,由编译器产生,不存在于源代码中。
ACC_ANNOTATION 0×2000 注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT
ACC_ENUM 0×4000 枚举类型
this_class

该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录当前类的全限定名。

super_class

该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录父类的全限定名。

interfaces

interfaces包含interfaces_count和interfaces[interfaces_count]。interfaces_count类型是u2,占2个字节。记录当前类所实现的接口数量。

interfaces[interfaces_count]是一组u2类型数据的集合。记录了当前类实现了哪些接口。如果没有实现过接口,就没有interface这一项。

field_info

fields_count结构同上,记录了当前类所定义的变量的总数量。包括类成员变量和类变量(静态变量)。

fields[fields_count]这里记录类中所定义的各个变量的详细信息,包括变量名,变量类型,访问标识,属性等。

field_info

类型

名称

说明

u2

access_flags

记录字段的访问权限。

u2

name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。表示变量的名称引用。

u2

descriptor_index

constant_pool中的索引,CONSTANT_Utf8_info类型,表示变量的类型信息引用。

u2

attributes_count

attributes包含的项目数。

attribute_info

attributes[attributes_count]

字段中包含的Attribute集合。

其中access_flags有如下的可选项。

字段的访问权限

标志名称

含义

ACC_PUBLIC

0x0001

pubilc,包外可访问。

ACC_PRIVATE

0x0002

private,只可在类内访问。

ACC_PROTECTED

0x0004

protected,类内和子类中可访问。

ACC_STATIC

0x0008

static,静态。

ACC_FINAL

0x0010

final,常量。

ACC_VOILATIE

0x0040

volatile,直接读写内存,不可被缓存。不可和ACC_FINAL一起使用。

ACC_TRANSIENT

0x0080

transient,在序列化中被忽略的字段。

ACC_SYNTHETIC

0x1000

synthetic,由编译器产生,不存在于源代码中。

ACC_ENUM

0x4000

enum,枚举类型字段

method_info

method_info包含methods_count和methods[methods_count];methods_count类型是u2,占2个字节。描述类中共包含多少个方法。methods_count后面是methods数组。该数组格式如下表:

methods结构

类型

名称

说明

u2

access_flags

记录方法的访问权限。

u2

name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定方法名称。

u2

descriptor_index

constant_pool中的索引,CONSTANT_Utf8_info类型,指定方法的描述符

u2

attributes_count

attributes包含的项目数。

attribute_info

attributes[attributes_count]

字段中包含的Attribute集合。

methods数组各项结构与fields类似,这里不再赘述。

其中,access_flags的可选值如下:

方法的访问权限

标志名称

标志值

说明

ACC_PUBLIC

0x0001

pubilc,包外可访问。

ACC_PRIVATE

0x0002

private,只可在类内访问。

ACC_PROTECTED

0x0004

protected,类内和子类中可访问。

ACC_STATIC

0x0008

static,静态。

ACC_FINAL

0x0010

final,不可被重写。

ACC_SYNCHRONIZED

0x0020

synchronized,同步方法。

ACC_BRIDGE

0x0040

bridge方法,由编译器生成。

ACC_VARARGS

0x0080

包含不定参数个数的方法。

ACC_NATIVE

0x0100

native,非Java语言实现的方法。

ACC_ABSTRACT

0x0400

abstract,抽象方法。

ACC_STRICT

0x0800

strictfp,设置floating-point模式为FP-strict

ACC_SYNTHETIC

0x1000

synthetic,由编译器产生,不存在于源代码中。

Attribute

属性表集合

在class文件中,属性表,方法表中都可以包含自己的属性表,用于描述某些场景的专有信息。为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的几种属性,除此以外,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类文件,字段表,方法表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
LineNumberTable Code属性

Java源码的行号与字节码指令的对应关系

LocalVariableTable Code属性 方法的局部变量描述
SourceFile 类文件 源文件名称
Synthetic 类文件,字段表,方法表 标识方法或字段是由编译器自动生成的
限于篇幅,每种属性的结构就不介绍了。感兴趣的同学可以查看 JVM官方文档。

      到此,我们分析了一个具体的Java程序生成的字节码文件的格式和内容。了解class文件结构可以帮助我们更深入的理解JVM的运行原理。本文限于篇幅,很多细节的地方没有分析到位,但是JVM定义的内容大致都有介绍,详细的信息大家可以参考JVM官方文档。

你可能感兴趣的:(Java基础)