JAVA虚拟机(三)- 图解类文件结构

版权声明:欢迎转载,标明出处 https://blog.csdn.net/mz4138/article/details/81984325

类文件结构

总览图-类文件结构

结构图

java源文件,编译为class 之后. class文件的数据结构

类文件结构

将下面这段java源文件为例,分析class文件的数据结构

java 代码

@Deprecated
public class ClassStruct implements Serializable {

    private int number = 3;

    public int getValue(){
        return number;
    }

}

字节码

用UltraEdit x64 下, 16进制打开 ClassStruct.class 文件内容如下

基址:      二进制内容:                                       UTF-8 内容:
00000000h: CA FE BA BE 00 00 00 34 00 1B 0A 00 04 00 16 09 ; 漱壕...4........
00000010h: 00 03 00 17 07 00 18 07 00 19 07 00 1A 01 00 06 ; ................
00000020h: 6E 75 6D 62 65 72 01 00 01 49 01 00 06 3C 69 6E ; number...I...<in
00000030h: 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 ; it>...()V...Code
00000040h: 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 ; ...LineNumberTab
00000050h: 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 ; le...LocalVariab
00000060h: 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 ; leTable...this..
00000070h: 15 4C 63 6F 6D 2F 61 79 61 2F 43 6C 61 73 73 53 ; .Lcom/aya/ClassS
00000080h: 74 72 75 63 74 3B 01 00 08 67 65 74 56 61 6C 75 ; truct;...getValu
00000090h: 65 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 ; e...()I...Source
000000a0h: 46 69 6C 65 01 00 10 43 6C 61 73 73 53 74 72 75 ; File...ClassStru
000000b0h: 63 74 2E 6A 61 76 61 01 00 0A 44 65 70 72 65 63 ; ct.java...Deprec
000000c0h: 61 74 65 64 01 00 19 52 75 6E 74 69 6D 65 56 69 ; ated...RuntimeVi
000000d0h: 73 69 62 6C 65 41 6E 6E 6F 74 61 74 69 6F 6E 73 ; sibleAnnotations
000000e0h: 01 00 16 4C 6A 61 76 61 2F 6C 61 6E 67 2F 44 65 ; ...Ljava/lang/De
000000f0h: 70 72 65 63 61 74 65 64 3B 0C 00 08 00 09 0C 00 ; precated;.......
00000100h: 06 00 07 01 00 13 63 6F 6D 2F 61 79 61 2F 43 6C ; ......com/aya/Cl
00000110h: 61 73 73 53 74 72 75 63 74 01 00 10 6A 61 76 61 ; assStruct...java
00000120h: 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 14 6A ; /lang/Object...j
00000130h: 61 76 61 2F 69 6F 2F 53 65 72 69 61 6C 69 7A 61 ; ava/io/Serializa
00000140h: 62 6C 65 00 21 00 03 00 04 00 01 00 05 00 01 00 ; ble.!...........
00000150h: 02 00 06 00 07 00 00 00 02 00 01 00 08 00 09 00 ; ................
00000160h: 01 00 0A 00 00 00 38 00 02 00 01 00 00 00 0A 2A ; ......8........*
00000170h: B7 00 01 2A 06 B5 00 02 B1 00 00 00 02 00 0B 00 ; ?.*.?.?......
00000180h: 00 00 0A 00 02 00 00 00 06 00 04 00 09 00 0C 00 ; ................
00000190h: 00 00 0C 00 01 00 00 00 0A 00 0D 00 0E 00 00 00 ; ................
000001a0h: 01 00 0F 00 10 00 01 00 0A 00 00 00 2F 00 01 00 ; ............/...
000001b0h: 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0B ; .....*?.?.....
000001c0h: 00 00 00 06 00 01 00 00 00 0C 00 0C 00 00 00 0C ; ................
000001d0h: 00 01 00 00 00 05 00 0D 00 0E 00 00 00 03 00 11 ; ................
000001e0h: 00 00 00 02 00 12 00 13 00 00 00 00 00 14 00 00 ; ................
000001f0h: 00 06 00 01 00 15 00 00                         ; ........

约定规则:
1. u1 u2 u4 分别表示 无符号整数1字节,无符号整数2字节,无符号整数4字节
2. 关于属性表和访问标志,全表内容请参见<深入理解JAVA虚拟机>第6章。 因为版面原因,只附上例子能用的上的表内容

魔数

魔数长度为4个字节: CA FE BA BE. 唯一的作用就是标志这是一个CLASS文件。

主次版本

  1. 次版本: [000000000+4] 2个字节,00 00
  2. 主版本: [000000000+4] 2个字节,00 34

主版本 34 的 10进制为 52 表示 JDK8

对应的版本
版本对应

常量池

JAVA 里面有很多常量池的概念。 这里的常量池是类文件常量池。

通常在JAVA里面说的常量池是 方法区中的运行时常量池 . 他们不是同一个概念 .

常量池是一个很复杂的表结构

常量池结构

  1. 常量数量
  2. 常量自定义结构

拿 html 的table 举例:

table 表示常量池
但是常量池下面的结构却不仅仅是tr,tbody,thead.
而是有14个以上的数据结构。 每个数据结构的子结构都不相同
例如:

<常量池>
    <字符串表 长度="10">
      qwertasdfg
    </字符串表>
    <方法引用>
        <类表 常量池索引="9">
        </类表>
        <名字类型表 常量池索引="13">
        </名字类型表>
    </方法引用>
</常量池>

事实上常量池的结构是非常紧凑的,和上面的描述完全不一样

接下来分析当前类的前6个属性表,并附上属性表图

简写常量表

常量 项目 类型 描述
CONSTANT_Utf8_info tag u1 值为1
length u2 UTF-8字符串编码长度
bytes length 长度为length的UTF-8编码字符串
CONSTANT_Class_info tag u1 值为7
index u2 指向全限定名的常量池索引
CONSTANT_Field_info tag u1 值为9
index u2 指向方法的类描述符 CONSTANT_Class_info的索引项
index u2 指向方法的类描述符 CONSTANT_NameAndType 的索引项
CONSTANT_Methodref_into tag u1 值为10
index u2 指向方法的类描述符 CONSTANT_Class_info的索引项
index u2 指向方法的类描述符 CONSTANT_NameAndType 的索引项

常量池数量

000000000+0x8 的两个字节 00 1B 表示常量池的总数: 1B 换成10进制就是 27.

表示常量池的数量是 27 - 1 = 26 个常量。

常量池下标从 1 开始。 0是预留的,用来表示不引用常量池的任何常量的意思

方法引用表

000000000+0xA: 0x0A 这里表示类型:CONSTANT_Methodref_info 表示是一个方法引用表

根据简写常量表的内容, 那么后面跟着的 两个u2类型的就是常量池索引00 04 00 16 分别是 类表索引和 名字类型表索引

接下来的层次分析如图:

方法引用表图

字段引用表

000000000+0xE: 0x09 这里表示类型:CONSTANT_Fieldref_info 表示是一个字段引用表

根据简写常量表的内容, 那么后面跟着的 两个u2类型的就是常量池索引00 03 00 17 和方法引用表的结构一模一样

字段引用表图

类表

接下来的三个类表分别指向索引为 18,19,1A 索引的常量

07 00 18 
07 00 19 
07 00 1A

类表图

字符串表

接下来的字符串表: 01 00 06 6E 75 6D 62 65 72

  1. 01标志:表示是一个字符串
  2. 长度为6
  3. 内容: number

字符串表

后面还有很多相同的结构不一一赘述了. 使用javap -v com/aya/ClassStruct 帮我们反编译出常量池的具体内容如下:

Cconstant pool:
   #1 = Methodref          #4.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#23         // com/aya/ClassStruct.number:I
   #3 = Class              #24            // com/aya/ClassStruct
   #4 = Class              #25            // java/lang/Object
   #5 = Class              #26            // java/io/Serializable
   #6 = Utf8               number
   #7 = Utf8               I
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/aya/ClassStruct;
  #15 = Utf8               getValue
  #16 = Utf8               ()I
  #17 = Utf8               SourceFile
  #18 = Utf8               ClassStruct.java
  #19 = Utf8               Deprecated
  #20 = Utf8               RuntimeVisibleAnnotations
  #21 = Utf8               Ljava/lang/Deprecated;
  #22 = NameAndType        #8:#9          // "<init>":()V
  #23 = NameAndType        #6:#7          // number:I
  #24 = Utf8               com/aya/ClassStruct
  #25 = Utf8               java/lang/Object
  #26 = Utf8               java/io/Serializable

访问标志

常量池结束后的u2类型的字段00 21表示类的访问标志。

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_SUPER 0x0020 是否允许使用 invokespecial指令的新语义,在JDK 1.0.2 之后标志必须为真

这里就已经明确了类的访问标志 : public

类父类接口索引

类索引 00 03 ,通过常量池表得到是: com/aya/ClassStruct

父类索引 00 04 ,通过常量池表得到是: java/lang/Object

接口索引 是一个表:
00 01 00 05 表示实现了一个接口,接口的常量池索引是5.

通过常量池表得到是: java/io/Serializable

字段表集合

00 01 00 02 00 06 00 07 00 00
1. 00 01 字段数量
2. 00 02 访问标志
3. 00 06 名字索引
4. 00 07 描述符索引
5. 00 00 属性表

字段访问标志表:

标志名称 标志值 含义
ACC_PRIVATE 0x0001 字段是否为private

描述符标志:

标志名称 含义
B 基本类型byte
C 基本类型char
I 基本类型int
L 对象类型,如Ljava/lang/Object

字段图

方法表集合

位置: 00000150h+0x7

方法表: 00 02 00 01 00 08 00 09 00 01 00 0A

含义:
1. 00 02 两个方法
2. 00 01 访问标志
3. 00 08 名称索引
4. 00 09 描述符索引
5. 00 01 属性表数量
6. 00 0A 属性表名称索引

方法访问标志

标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否为public

根据前面3个的内容
01 08 09 ,获得方法信息: public V() (){}

表示类的构造方法。也就是:public ClassStruct(){}.

无参构造是由javac编译器编译成class文件时,写入字节码中的

这里只有方法的描述,那方法的内容去哪里了呢? 方法的内容在方法的属性表里面

表示无参构造有一个属性表, 属性表的名称索引0x0A 引用的是 Code 字符串。

code属性表结构

类型 名称 数量 含义
u2 attribute_name_index 1 属性表索引. Code 表示Code属性表
u4 attribute_length 1 属性表长度
u2 max_stack 1 操作数栈数量
u2 max_locals 1 局部变量表数量
u4 code_length 1 代码长度
u2 code code_length 代码内容
u2 exception_table_length 1 异常表长度
u2 exception_table exception_table_length 异常表, 表示 catch 捕捉的字节码区域
u2 attribute_count 1 Code属性表的属性表
u2 attribute attribute_count Code属性表的属性表长度

方法内容

  1. 属性表索引: 00 0A 字符串:Code
  2. 属性表长度 00 00 00 38 长度为:56
  3. 操作数栈数量: 00 02 操作数栈为2
  4. 局部变量表数量: 00 01 局部表量表为1 (this占用的局部变量表第一个)\
  5. 代码长度: 00 00 00 0A 长度为:10
  6. 代码内容: 2A B7 00 01 2A 06 B5 00 02 B1
  7. 异常表长度: 00 00
  8. 属性表长度: 00 02

代码内容已经确定了, 为什么还有属性表呢? 属性表有什么内容呢?

方法内容属性表-行号表

行号表:
0B 00 00 00 0A 00 02 00 00 00 06 00 04 00 09 00
1. LineNumberTable
2. 长度为 10
3. 内容:00 02 00 00 00 06 00 04 00 09 00

第一个00 0B 对应常量池的LineNumberTable, 记录了java源文件和class文件的行号对应。

可以通过参数取消生成行号表, 取消行号表之后就无法调试了

后面行号表的具体内容就不进行分析了

方法内容属性表-本地变量表

本地变量表: 0C 00 00 00 0C 00 01 00 00 00 0A 00 0D 00 0E 00 00 00
1. 类型标志:0C 常量池索引,对应的常量 LocalVariableTable
2. 长度为 0xC 长度为12
3. 内容: 00 01 00 00 00 0A 00 0D 00 0E 00 00 00

方法属性表

方法表2-getValue

01 00 0F 00 10 00 01 00 0A

  1. 00 01 访问标志 public
  2. 00 0F 名称索引 getValue
  3. 00 10 描述符索引 ()I
  4. 00 01 属性表数量 1
  5. 00 0A 属性表名称索引 Code

和构造方法一样的分析.

方法定义: public int getValue();

这里不再赘述方法结构的具体内容

这里通过00 00 00 2F 获得属性表长度为 47. 进行接下来的分析

000001b0h:                   01 00 0A 00 00 00 2F 00 01 00 ; ............/...
000001b0h: 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0B ; .....*?.?.....
000001c0h: 00 00 00 06 00 01 00 00 00 0C 00 0C 00 00 00 0C ; ................
000001d0h: 00 01 00 00 00 05 00 0D 00 0E 00 00

类的属性表

类属性表内容:

                                               00 03 00 11 ; ................
000001e0h: 00 00 00 02 00 12 00 13 00 00 00 00 00 14 00 00 ; ................
000001f0h: 00 06 00 01 00 15 00 00

00 03, 表示类的属性表有3个属性

类的属性表的相关含义:

标志 长度 内容
常量池索引0x11,对应 SourceFile 0x02 常量池索引0x12,对应: ClassStruct.java
常量池索引0x13,对应 Deprecated 0x00 表示类被标记为@Deprecated
常量池索引0x14,对应 RuntimeVisibleAnnotations 0x06 内容: 00 01 00 15 00 00

类属性表图:

类属性表

总结

到此为止,图解class文件结构就结束了。

关于字节码的内容,行号属性表 等相关内容,都没有深入的说明,这里只是为了让新手更容易理解class文件结构而已。

要深入理解还是要 看书+Java虚拟机规范 才可以

猜你喜欢

转载自blog.csdn.net/mz4138/article/details/81984325