Java编译后的class文件解析

news/2024/7/4 1:41:04 标签: class解析, class, JVM, class文件
class="baidu_pl">
class="article_content clearfix">
class="markdown_views prism-atom-one-dark">

文章已同步github博客:Java编译后的class文件解析

1、编译Java类

1.1、写Java类

编写一份Java类,即.java文件,例如:

package com.jesus.util;

public class TestDemo {

    public static final String SUCCESS = "success";

    public static void main(String[] args) {
        System.out.println("main");
    }

    private String printText(String str) {
        System.out.println(str);
        return SUCCESS;
    }
}

1.2、编译

通过javac命令编译为class文件:

javac DemoTest.java

class_31">2、.class文件结构

生成的.class文件如下:

cafe babe 0000 0034 0023 0a00 0700 1509
0016 0017 0800 0f0a 0018 0019 0700 1a08
001b 0700 1c01 0007 5355 4343 4553 5301
0012 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 0100 0d43 6f6e 7374 616e 7456
616c 7565 0100 063c 696e 6974 3e01 0003
2829 5601 0004 436f 6465 0100 0f4c 696e
654e 756d 6265 7254 6162 6c65 0100 046d
6169 6e01 0016 285b 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956 0100 0970
7269 6e74 5465 7874 0100 2628 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 294c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b01 000a 536f 7572 6365 4669 6c65 0100
0d54 6573 7444 656d 6f2e 6a61 7661 0c00
0b00 0c07 001d 0c00 1e00 1f07 0020 0c00
2100 2201 0017 636f 6d2f 6a65 7375 732f
7574 696c 2f54 6573 7444 656d 6f01 0007
7375 6363 6573 7301 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 106a 6176
612f 6c61 6e67 2f53 7973 7465 6d01 0003
6f75 7401 0015 4c6a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 3b01 0013 6a61
7661 2f69 6f2f 5072 696e 7453 7472 6561
6d01 0007 7072 696e 746c 6e01 0015 284c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b29 5600 2100 0500 0700 0000 0100 1900
0800 0900 0100 0a00 0000 0200 0600 0300
0100 0b00 0c00 0100 0d00 0000 1d00 0100
0100 0000 052a b700 01b1 0000 0001 000e
0000 0006 0001 0000 0003 0009 000f 0010
0001 000d 0000 0025 0002 0001 0000 0009
b200 0212 03b6 0004 b100 0000 0100 0e00
0000 0a00 0200 0000 0800 0800 0900 0200
1100 1200 0100 0d00 0000 2600 0200 0200
0000 0ab2 0002 2bb6 0004 1206 b000 0000
0100 0e00 0000 0a00 0200 0000 0c00 0700
0d00 0100 1300 0000 0200 14

class_74">2.1、.class文件组成

以上可知,.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在.class文件之中,中间没有添加任何分隔符;

根据Java虚拟机规范的规定,.class文件格式采用一种类似于C语言的伪结构来存储数据,包含无符号数和表:

  • 无符号数:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值;
  • 表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以”_info“结尾。

Class文件的结构没有分隔符,无论你是数量还是顺序,都是严格规定的,哪个字节代表什么含义、长度多少、先后顺序如何,都不允许改变。

class_84">2.2、.class文件结构

ClassFile {
  u4             magic; // 魔数,固定值 0xCAFEBABE
  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]; // 属性表
}

2.2.1、魔数

魔数(Magic Number),u4,即class文件中用4个字节表示,他的唯一作用就是确定这个文件是否为一个能否被虚拟机所识别的class文件,魔数的固定值为0xCAFEBABE,不会改变;

2.2.2、文件版本号

紧挨着魔数的2个字节为副版本号minor_version(上面class文件例子中0x0000),接下来的2个字节为主版本号major_version(上面例子中的0x0034,换算成10进制为52,查询jdk版本对应关系,主版本52为jdk 1.8);

2.2.3、常量池计数器

接下来的2个字节表示常量池计数器constant_pool_count,常量池中的数量不固定,constant_pool_count的值 = 常量池中的成员数 + 1,常量池的索引从1开始;

例如上面class文件中的16进制数为0x0023,换算成10进制为35,通过命令“javap -v DemoTest.class"来查看常量池,上面class文件的常量池内容如下:

xxxMacBook-Pro:JVM_test xxx$ javap -v TestDemo.class 
Classfile /Users/xxx/xxx/TestDemo.class
  Last modified 2019-12-4; size 603 bytes
  MD5 checksum 47bd6066637c077873b14b73e810b1e2
  Compiled from "TestDemo.java"
public class com.jesus.util.TestDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #15            // main
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/jesus/util/TestDemo
   #6 = String             #27            // success
   #7 = Class              #28            // java/lang/Object
   #8 = Utf8               SUCCESS
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               ConstantValue
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               printText
  #18 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #19 = Utf8               SourceFile
  #20 = Utf8               TestDemo.java
  #21 = NameAndType        #11:#12        // "<init>":()V
  #22 = Class              #29            // java/lang/System
  #23 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
  #24 = Class              #32            // java/io/PrintStream
  #25 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/jesus/util/TestDemo
  #27 = Utf8               success
  #28 = Utf8               java/lang/Object
  #29 = Utf8               java/lang/System
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;
  #32 = Utf8               java/io/PrintStream
  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V
{
  public static final java.lang.String SUCCESS;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String success

  public com.jesus.util.TestDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String main
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8
}
SourceFile: "TestDemo.java"

以上Constant pool中的常量有34个,+1正好就等于constant_pool_count;

2.2.4、常量池

常量池是class文件中占用空间最大的数据项目之一,由于常量池中的常量数量不固定,所以在常量池前面需要一个u2类型的数据,代表其大小,Java虚拟机指令不依赖于类、接口、类实例或数组的运行时布局,而是依赖常量池表中的符号信息,常量池的表结构如下(常量池中的每一项常量都是一个表):

cp_info {
  u1 tag; // 标志位,用于区分常量类型
  u1 info[];
}

常量池表中,开始都是由一个u1类型的tag标志位开始,代表这个常量的类型,后面的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_MothodType_info                  16          标志方法类型
CONSTANT_MethodHandle_info                15          表示方法句柄
CONSTANT_InvokeDynamic_info               18          表示一个动态方法调用点
2.2.4.1、CONSTANT_Methodref_info

如上面class文件中,接下来的1个字节tag标志位为0x0a,换算成10进制为10,由上表可知类型为CONSTANT_Methodref_info,即类中方法的符号引用,查询可知他的结构为:

CONSTANT_Methodref_info {
	u1 tag; // 标志位,值为10
	u2 class_index; // 该方法所属的类在常量池里的索引
	u2 name_and_type_index; // 该方法的名称和类型的索引
}

其中class_index的值必须是常量池中的有效索引,并且索引的项必须为CONSTANT_Class_info结构,表示一个类或者接口,当前方法是这个类或接口的成员,如上面class文件中的0x0007,换算成10进制为7,即常量池索引为7,查看上面常量池索引为7的类型,正好是一个Class类型;

name_and_type_index的值也必须是常量池中的有效索引,并且索引的项必须为CONSTANT_NameAndType_info结构,表示当前方式的名字和描述符,上面class文件中接下来的2个字节为0x0015,十进制数为21,索引21的类型为NameAndType;

此时看常量池中的第一个常量Methodref类型,后面指向#7,#21,正好对应上面的分析;

2.2.4.2、CONSTANT_Fieldref_info

接下来继续取1个字节的u1类型,0x09,十进制为9,对应类型为CONSTANT_Fieldref_info,即字段索引,字段索引的结构与CONSTANT_Methodref_info结构一样,后面继续取2个字节的u2类型的class_index,0x0016,十进制为22,也对应Class类型,后面2个字节0x0017,十进制为23,取常量池索引为23的即NameAndType类型,常量池中他指向#22,#23;

2.2.4.3、CONSTANT_String_info

继续取1个字节的u1标志位,0x08,即CONSTANT_String_info类型,结构为:

CONSTANT_String_info {
	u1 tag; // 标志位,值为8
	u2 string_index; // 指向字符串字面量的索引
}

string_index的值必须为常量池中的有效索引,其指向的成员必须是CONSTANT_utf8_info结构,表示Unicode码点序列,最终会被初始化成为一个String对象;

继续取2个字节的u2类型,0x000f,即十进制15,常量池中的索引为15的类型为Utf8,而第3个常量后面也是指向#15;

略过后面的0x0a 0018 0019,类型为CONSTANT_Methodref_info;

2.2.4.4、CONSTANT_Class_info

继续取后面1个字节,0x07,即7对应CONSTANT_Class_info类型,其结构为:

CONSTANT_Class_info {
	u1 tag;  // 标志位,值为7
	u2 name_index; // 全限定名常量的索引值
}

name_index值必须为常量池中的有效索引,其成员必须是CONSTANT_utf8_info结构,表示一个有效的类或接口二进制名称的内部形式;

取后面2个字节0x001a,即十进制26,指向常量池中的#26,即Utf8类型;

略过后面0x08 001b 07 001c

2.2.4.5、CONSTANT_utf8_info

继续取2个字节标志位0x01,对应类型为CONSTANT_utf8_info,其结构为:

CONSTANT_utf8_info {
	u1 tag; // 标志位,值为1
	u2 length; // utf-8编码的字符串占用的字节数
	u1 bytes[length]; // 长度为length的utf-8编码的字符串
}

length值指明了bytes[]数组的长度,其确定CONSTANT_utf8_info结构中的内容长度;

bytes[]表示字符串值的byte数组;

如上class文件,继续取2个字节的length长度值0x0007,即十进制7,表示长度为7,即后面继续取7个u1类型作为常量值,即0x5355 4343 4553 53,对比ASCII码值表,该常量为“SUCCESS”,即在java文件中声明的常量值;

略过后面的0x01 0012 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b,“Ljava/lang/String”;

略过后面的0x01 000d 43 6f6e 7374 616e 7456 616c 7565,“ConstantValue”;

略过后面的0x01 0006 3c 696e 6974 3e, “”;

0x01 0003 2829 56,“()V”;

…后面几个Utf8略过;

此处,由于class文件中方法、字段等都需要引用CONSTANT_Utf8_info类型常量来描述名称,所以CONSTANT_Utf8_info类型常量的最大长度就是length的最大值,即u2类型能表达的最大值,0xffff,对应十进制数为65535,也就是Java中方法或变量的最大数量不能超过64KB,即65535;

2.2.4.6、CONSTANT_NameAndType_info

继续取后面的0x0c,对应类型为CONSTANT_NameAndType_info,其结构为:

CONSTANT_NameAndType_info {
	u1 tag; // 标志位,值为12
	u2 name_index; // 该字段或方法名称常量的索引
	u2 descriptor_index; // 该字段或方法描述符常量的索引
}

其中name_index的值必须是常量池中的有效索引,索引处的成员必须是CONSTANT_utf8_info结构,该结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名,如上面class文件中,0x000b,即十进制11,对应常量池中#11,Utf8类型;

descriptor_index的值必须是常量池中的有效索引,索引处的成员必须是CONSTANT_utf8_info结构,表示一个有效的字段描述符或方法描述符,如上面class文件中的0x000c,即十进制12,对应常量池中#12,即Utf8类型;

略过后面0x07 001d;

…后面几个NameAndType、Class、Utf8略过,直到将常量池中的所有常量都解析完毕;

2.2.4.7、CONSTANT_Integer_innfo
{
	u1 tag; // 标志位,值为3
	u4 bytes; // 按照高位在浅存储的int值
}
2.2.4.8、CONSTANT_Float_info
{
	u1 tag; // 标志位,值为4
	u4 bytes; // 按照高位在浅存储的float值
}
2.2.4.9、CONSTANT_Long_info
{
	u1 tag; // 标志位,值为5
	u8 bytes; // 按照高位在浅存储的long值
}
2.2.4.10、CONSTANT_Double_info
{
	u1 tag; // 标志位,值为6
	u8 bytes; // 按照高位在浅存储的double值
}
2.2.4.11、CONSTANT_InterfaceMethodref_info
{
	u1 tag; // 标志位,值为11
	u2 index; // 声明方法的接口描述符CONSTANT_Class_info的索引项
	u2 index; // 指向名称及类型描述符CONSTANT_NameAndType的索引
}
2.2.4.12、CONSTANT__MethodHandle_info
{
	u1 tag; // 标志位,值为15
	u1 reference_kind; // 值为1~9,决定了方法句柄类型
	u2 reference_index; // 必须是常量池的有效索引
}
2.2.4.13、CONSTANT_MethodType_info
{
	u1 tag; // 标志位,值为16
	u2 descriptor_index; // 必须是常量池的有效索引,类型必须是CONSTANT_Utf8_info结构,表示方法描述符
}
2.2.4.14、CONSTANT_InvokeDynamic_info
{
	u1 tag; // 标志位,值为18
	u2 bootstrap_method_attr_index; // 值必须是当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
	u2 namee_and_type_index; // 必须是常量池的有效索引,结构必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述
}

2.2.5、访问标志

access_flags为访问标志,用于表示某个类或者接口的访问权限及属性,是类还是接口等,取值对应如下:

  标志名                值          含义
ACC_PUBLIC           0x0001     声明为public,可以包外访问
ACC_FINAL            0x0010     声明为final,不允许有子类
ACC_SUPER            0x0020     当用到invokespecial指令时,需要对父类方法做特殊处理
ACC_INTERFACE        0x0200     该class文件定义的是接口而不是类
ACC_ABSTRACT         0x0400     声明为abstract,不能被实例化
ACC_SYNTHETIC        0x1000     声明为synthetic,表示该class文件并非由Java源代码所生成
ACC_ANNOTATION       0x2000     标识注解类型
ACC_ENUM             0x4000     标识枚举类型

取后面2个字节u2类型,即0x0021,发现并未找到对应的值,其实该值为所有访问标志通过或运算得来的值,0x0021=0x0001|0x0020,即public和super,通过javap命令打印出的类标志符也为:flags: ACC_PUBLIC, ACC_SUPER,正好对应;

对于Java SE 8及后续版本,无论class文件中的这个标志的实际值是什么,也不管class文件的版本号是多少,Java虚拟机都认为每个class文件均设置了ACC_SUPER标志;

2.2.6、类索引

类索引,u2类型,this_class的值必须是常量池中某个有效索引,并且索引处的成员必须为CONSTANT_Class_info结构,表示class文件所定义的类或者接口,如上面class文件,继续取后面2个字节的u2类型,0x0005,对应常量池中的#5,即Class类型;

2.2.7、父类索引

父类索引,u2类型,super_class值要么是0,要么是对常量池中某项的一个有效索引,如果不为0,在常量池中的引用处成员必须是CONSTANT_Class_info类型,表示这class文件所定义的类的直接超类,如果为0,那么这个class只可能用来表示Object类,因为他是唯一没有父类的类;

取上面class文件的2个字节u2类型,0x0007,十进制7,常量池中#7,即Class类型;

2.2.8、接口计数器

接口计数器,u2类型,interfaces_count的值表示接口索引表的容量,即当前类或者接口的直接超接口数量,取上面class文件的2个字节u2类型,0x0000,即0,没有接口,该值为0时,接口表不占用任何字节;

2.2.9、接口表

接口表,interfaces[]中每个成员的值必须是常量池中的某个有效索引,长度为interfaces_count,每个成员必须是CONSTANT_Class_info结构,由于接口数量为0,所以这里没有接口;

2.2.10、字段计数器

字段计数器,u2类型,fields_count表示当前class文件的字段数量,如上class文件,取2个字节u2类型,0x0001,即该类有1个成员字段,即java文件中定义了一个String类型的成员字段SUCCESS;

2.2.11、字段表

字段表fields[fields_count],用于描述接口或类中声明的变量,其中每个成员的结构均为field_info,用于描述当前类或者接口中某个字段的完整描述,只描述当前类或接口,不包括父类或父接口集成的字段,结构如下:

field_info {
	u2              access_flags;
	u2              name_index;
	u2              descriptor_index;
	u2              attributes_count;
	attributes_info attributes[attributes_count];
}

其中access_flags表示子弹的访问权限和基本属性,对应的含义如下:

   标志名            值            说明
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     0x1000       被标识的字段由编译器产生,而没有写源代码中
ACC_ENUM          0x4000       枚举的成员

如上class文件中,取字段数量后面的u2类型的access_flags,即0x0019,即0x0019=0x0001|0x0008|0x0010,对应PUBLIC,STATIC,FINAL,正对应java文件的SUCCESS字段的访问限定符”public static final“;

name_index的值必须为常量池中的一个有效索引,该处的成员必须是CONSTANT_Utf8_info结构,表示一个有效的非限定名,这个名词对应于本字段,取2个字节的u2类型,即0x0008,对应常量池中的#8,即Utf8类型,对应值为”SUCCESS“,即字段名;

descriptor_index的值必须为常量池的一个有效索引,类型必须是CONSTANT_Utf8_info,表示一个有效的字段描述符,用来描述字段的数据类型、方法的参数列表(包括数量、类型及顺序)和返回值,取2个字节的u2类型,0x0009,即对应常量池的#9,Utf8类型,返回值用一个大写字符来表示,如下:

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

另外,对于数组类型,每一位度将使用一个前置”[“字符来描述,例如定义一个String[][]的二维数组,将被表示为”[[Ljava/lang/String;“,整型数组为”[I“;

方法描述的顺序为:先参数列表后返回值,参数列表按照参数的严格顺序放在一组小括号”()“内,例如:

int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述为:”([CII{CIII)I“;

接着取后面u2类型的2个字节0x0009,十进制为9,常量池中#9为Utf8,值为”Ljava/lang/String;“,即该变量为String类型;

attributes_count的值表示当前字段的附加属性的数量,取2个字节的u2类型,0x0001,即有一个附加属性;

attributes[]属性表中的每个成员,一个字段可以关联任意多个属性(属性见下文2.2.15);

取2个字节的attribute_name_index,0x000a,十进制为10,对应常量池有效索引#10,对应Utf8类型,即”ConstantValue“(属性见后文2.2.15.1);

ConstantValue的结构为(后面分析):

{
	u2 attribute_name_index; // 标识
	u4 attribute_length; // 固定为2
	u2 constantvalue_index; // 常量池中的一个有效索引
}

根据ConstantValue的结构,attribute_length固定为2,取u4类型的4个字节,0x0000 0002(见后文2.2.15.1);

继续取u2类型的2个字节ConstantValue的constantvalue_index,即0x0006,对应常量池#6,即String类型,值为”success“;

2.2.12、方法计数器

方法计数器的结构为u2类型,即表示该类或接口中定义了多少个方法;

如上class文件中,取u2类型的2个字节0x0003,即对应该类中定了三个方法,观察java文件对应的方法为2个,类中未显示定义无参构造方法时,编译器会默认加上,所以为3个方法;

2.2.13、方法表

方法表中的方法描述和字段描述几乎一样,同上,方法吧的结构为:

{
	u2             access_flags; // 访问限定符
	u2             name_index; // 名称索引
	u2             descripotr_index; // 描述符索引
	u2             attributes_count; // 属性数量
	attribute_info attributes; // 属性表集合
}

方法表访问标志符如下表:

  标志名称             标志值          含义
ACC_PUBLIC           0x0001     方法是否为public
ACC_PRIVATE          0x0002     方法是否为private
ACC_PROTECTED        0x0004     方法是否为protected
ACC_STATIC           0x0008     方法是否为static
ACC_FINAL            0x0010     方法是否为final
ACC_SYNCHRONIZED     0x0020     方法是否为synchronized
ACC_BRIDGE           0x0040     方法是否由编译器产生的桥接方法
ACC_VARARGS          0x0080     方法是否接受不定参数
ACC_NATIVE           0x0100     方法是否为native
ACC_ABSTRACT         0x0400     方法是否为abstract
ACC_STRICTFP         0x0800     方法是否为strictfp
ACC_SYNTHETIC        0x1000     方法是否是由编译器自动产生的

同字段表描述一样,取u2类型2个字节0x0001的access_flags,即对应PUBLIC;

u2类型的2个字节的name_index,0x000b,十进制为11,常量池中#11为Utf8类型,对应值为,即实例构造器;

u2类型的2个字节的descripotr_index,0x000c,十进制为12,常量池中#12为Utf8,值为”()V“,即无参的返回值为void;

u2类型的2个字节的attributes_count,0x0001,即一个属性值;

取u2类型的2个字节的属性标识,0x000d,十进制为13,常量池中#13为Utf8,值为”Code“,该值为固定值(见下文属性2.2.15.2);

u4类型4个字节attribute_length,0x0000 001d,对应十进制29,属性值的长度为29,往后29个字节都是该方法的描述即0x0001 0001 0000 0005 2a b7 00 01 b1 0000 0001 000e 0000 0006 0001 0000 0003;

u2类型max_stacks,0x0001,操作数栈深度为1,u2类型max_locals,0x0001,局部变量空间为1(对应javap生成的内容可验证);

u4类型字节码长度,0x0000 0005,即字节码长度为5;

连续取5个u1类型的字节码,0x2a、0xb7、0x00、0x01、0xb1,查询虚拟机字节码指令表可知分别对应:aload_0、invokespecial、return;

字节码指令详见《深入理解Java虚拟机·附录B 虚拟机字节码指令表》

  • aload_0:将第一个引用类型本地变量推送至栈顶;
  • invokespecial:调用超类构造方法,实例初始化方法,私有方法,该方法有一个u2类型的参数说明,即后面0x0001,结构为CONSTANT_Utf8_info,常量池中#1值为”java/lang/Object.""😦)V“,由此可知为无参的构造方法;
  • return:从当前方法返回void,执行这条指令后,当前方法结束;

由上分析可对应javap生成的内容验证,对应3条指令:

Code:
	stack=1, locals=1, args_size=1
		0: aload_0
		1: invokespecial #1                  // Method java/lang/Object."<init>":()V
		4: return

备注:上面locals=1,args_size=1,但是方法没有参数,在非static方法中,默认有一个this参数;

备注:字节码指令解析时,需要注意的是有些字节码指令是带参数的,如上面的invokespecial,后面跟着两个参数;

接着后面是u2类型的异常数量以及异常表,0x0000,即该方法没有异常表;

继续取u2类型的attribute_count,0x0001,即1个属性;

取u2类型2个字节属性名,0x000e,十进制为14,常量池中#14为Utf8,值为:”LineNumberTable“(见后文2.2.15.3);

根据下文中的LineNumberTable结构,取u4类型的4个字节的attribute_length,0x0000 0006,即属性值的长度为6,继续取u2类型的2个字节的line_number_table_length,0x0001,即line_number_table集合长度为1,line_number_table结构为2个u2类型,分别对应start_pc和line_number,即0x0000、0x0003,分别对应字节码指令行号为0,Java源码行号为3,与javap生成的内容对比验证一致:

LineNumberTable:
	line 3: 0

后面其他方法的分析,main方法:

0x0009 000f 0010 0001 000d 0000 0025 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0100 0e00 0000 0a00 0200 0000 0800 0800 09

printText方法:

0x0002 0011 0012 0001 000d 0000 0026 0002 0002 0000 000a b200 022b b600 0412 06b0 0000 0001 000e 0000 000a 0002 0000 000c 0007 000d

2.2.14、属性计数器

属性计数器,u2类型,表明该类的属性数量,例如本class文件的0x0001,即1个属性描述;

2.2.15、属性表

Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景专有的信息;

例如上面class文件中,分析到最后,字节码文件只剩下”0x0013 0000 0002 0014“,根据属性表的结构,取u2类型的2个字节的属性名,0x0013,十进制为19,常量池中#19为Utf8,值为”SourceFile“(下文2.2.15.4),接着看后面的SourceFile的结构分析,取u4类型的4个字节的属性长度,0x0000 0002,即两个长度,取u2类型的2个字节属性描述,0x0014,十进制为20,常量池中#20为Utf8,值为”TestDemo.java“;

通过验证javap生成的文件可知正好对应:

SourceFile: "TestDemo.java"

属性表的关键字常用部分如下(详见《深入理解Java虚拟机》):

属性名称              使用位置              含义
Code                 方发表          Java代码编译成的字节码指令
ConstantValue        字段表          final关键字定义的常量值
InnerClasses         类文件          内部类列表
LineNumberTable     Code属性        Java源码的行号与字节码指令的对应关系
localVariableTable  Code属性        方法的局部变量描述
...

对于每个属性,名称即标志位表示的,需要从常量池中引用一个CONSTANT_Utf8_info类型的常量表示,属性值的结构则由每个不同的属性自己独有的结构,需要一个u4长度的属性去说明属性值所占用的位数即可;

属性表的结构:

{
	u2 attribute_name_index; // 属性名
	u4 attribute_length; // 属性值的长度
	u1 info; // 属性
}
2.2.15.1、ConstantValue

只有被static关键字修饰的变量才使用这个属性,在虚拟机中,对非static类型的变量的赋值是在实例构造器方法中,而对于类变量(static修饰),则在类构造器方法或者使用ConstantValue属性;

目前Sun Javac编译器是如果同时使用final和static来修饰,并且这个数据类型为基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则会选择在方法中进行初始化;

ConstantValue的结构如下:

{
	u2 attribute_name_index; // 标识
	u4 attribute_length; // 固定为2
	u2 constantvalue_index; // 常量池中的一个有效索引
}
2.2.15.2、Code

Java程序方法体中的代码经过Javac编译后,最终变为字节码指令存储在Code属性内,保存在方法表中,并非所有方法表都必须有这个属性,如接口或者抽象类中的方法就不存在Code属性,Code属性的结构如下:

{
	u2             attribute_name_index; // 指向CONSTANT_Utf8_info的常量索引,固定为”Code“,表示属性名称
	u4             attribute_length; // 该属性值的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减去6个字节
	u2             max_stack; // 操作数栈(Operand Stacks)深度的最大值,根据该值来分配栈帧中的操作站深度
	u2             max_locals; // 局部变量表所需的存储空间,单位为Slot(不超过32位占用1个Slot,64位占2个)
	u4             code_length; // 字节码长度
	u1             code; // 存储java源程序编译后的字节码指令
	u2             exception_table_length; //
	exception_info exception_table; //
	u2             attribute_count; //
	attribute_info attributes; //
}
2.2.15.3、LineNumberTable

用于描述Java源代码行号与字节码行号之间的对应关系,并不是运行时必须的属性,但是默认会生成到class文件中,有这个信息在程序出现异常时,可以再堆栈中显示出出错的行号,结构如下:

{
	u2 arrtibute_name_index; // 属性名
	u4 attribute_length; // 属性长度
	u2 line_number_table_length; // line_number_table的长度
	line_number_info line_number_table; // 包括start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号
}
2.2.15.4、SourceFile

该属性用于记录生成这个class文件的源码文件名称,在Java中,大多数类名和文件名是一致的,内部类会有一些特殊,有高属性时,程序抛出异常时,会在堆栈中显示出代码所属的文件名,该属性为一个定长属性,结构如下:

{
	u2 attribute_name_index; // 属性名
	u4 attribute_length; // 属性长度
	u2 sourcefile_index; // 文件名索引
}

其余属性值具体见《深入理解Java虚拟机》,书中有详细介绍;

参考书籍
《深入理解Java虚拟机》
《Java虚拟机规范》


http://www.niftyadmin.cn/n/1568269.html

相关文章

Java字节码指令分析

文章已同步github博客&#xff1a;Java字节码指令分析 1、概念 分析字节码指令之前&#xff0c;先明确以下几个概念&#xff1b; 1.1、程序计数器 ​JVM中的程序计数器&#xff0c;执行非native方式时&#xff0c;程序计数器保存Java虚拟机正在执行的字节码指令地址&#x…

AIDL中的数据流向

文章已同步github博客&#xff1a;AIDL中的数据流向 1、AIDL文件 1.1、文件类型 文件后缀名为.aidl&#xff1b; 1.2、数据类型 1.2.1、默认支持类型 默认支持的类型不需要导包&#xff1b; Java八种基本类型&#xff1a;byte、short、int、long、float、double、boolea…

用户手势检测-GestureDetector使用详解

一、概述 当用户触摸屏幕的时候&#xff0c;会产生许多手势&#xff0c;例如down&#xff0c;up&#xff0c;scroll&#xff0c;filing等等。 一般情况下&#xff0c;我们知道View类有个View.OnTouchListener内部接口&#xff0c;通过重写他的onTouch(View v, MotionEvent even…

Zygote进程启动流程

博客同步至github博客&#xff1a;Zygote进程启动流程 使用到的相关源码&#xff1a;https://github.com/JesusYoung/AndroidResourceCode9.0/tree/master 基于Android 9.0 1、Zygote进程 Zygote是在init进程启动时创建的&#xff0c;它又称为孵化器&#xff0c;它可以通过…

SystemServer进程启动

文章已同步Github博客&#xff1a;SystemServer进程启动 使用到的相关源码&#xff1a;https://github.com/JesusYoung/AndroidResourceCode9.0/tree/master 基于Android 9.0 1、SystemServer进程作用 SystemServer进程主要是用于创建系统服务的&#xff0c;例如AMS、WMS、…

PorterDuff.Mode详解

做过图形图像处理coding的Android程序员一定用过或了解过PorterDuff.Mode这个枚举变量中的某些值&#xff0c;对此了解不多理解不深刻的时候是不是会很纠结到底该用那个模式呢&#xff1f;至少不能快速准确地用到恰当的模式&#xff0c;那么PorterDuff.Mode究竟是什么&#xff…

Launcher进程启动

文章已同步Github博客&#xff1a;Launcher进程启动 使用到的相关源码&#xff1a;https://github.com/JesusYoung/AndroidResourceCode9.0/tree/master 基于Android 9.0 1、Launcher Launcher作为Android系统的桌面&#xff0c;它的作用有两点&#xff1a; 作为Android系…

Android 8.0 SystemUI消息列表图标显示问题

Google为了统一风格&#xff0c;对消息列表的图标做了统一处理&#xff0c;设置消息icon的时候不能随便用一张带有色彩的图片&#xff0c;只能使用白色和透明两个颜色&#xff0c;具体设置代码位置在frameworks/base/core/java/android/app/Notification.java类中的processSmal…