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."<init>":()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."<init>":()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               <init>
  #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               <clinit>
  #26 = Utf8               SourceFile
  #27 = Utf8               SourceTest.java
  #28 = NameAndType        #17:#18        // "<init>":()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."<init>":()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 "<init>":()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使用十六进制查看

3、class文件结构详解

魔数

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

版本号

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

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

常量池

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

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

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

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

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

字段的符号引用

9CONSTANT_Methodref_info   10类中方法的符号引用
10CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
11CONSTANT_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_PUBLIC0×0001是否为pubilc类型
ACC_FINAL0×0010是否为final类型,只有类可设置
ACC_SUPER0×0020用于兼容早期编译器,新编译器都设置该标记,以在使用invokespecial指令时对子类方法做特定处理。
ACC_INTERFACE0×0200标识为接口。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM
ACC_ABSTRACT0×0400抽象类,无法实例化。不可与ACC_FINAL同时设置。
ACC_SYNTHETIC0×1000synthetic,由编译器产生,不存在于源代码中。
ACC_ANNOTATION0×2000注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT
ACC_ENUM0×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类文件内部类列表
LineNumberTableCode属性

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

LocalVariableTableCode属性方法的局部变量描述
SourceFile类文件源文件名称
Synthetic类文件,字段表,方法表标识方法或字段是由编译器自动生成的

限于篇幅,每种属性的结构就不介绍了。感兴趣的同学可以查看JVM官方文档

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

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值