Java虚拟机-类文件结构 类文件结构 实例 总结

Class类文件的结构

任何一个Class文件都对应着唯一一个类或者接口的定义信息,但是类或者接口并不一定都要定义在文件里(例如类也可以通过类加载器直接生成)。Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑地排列在Class文件中。Class文件格式采用类似C语言结构体的伪结构来存储数据,包括两种数据类型:无符号数和表。
无符号数属于基本的数据类型,以u1,u2,u4,u8来代表1,2,4,8个字节的无符号数。可以用来描述数字、索引引用、数量值或者按照utf-8编码构成字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以“_info”结尾。表用于描述有层次关系的符合结构数据。

魔数与Class文件的版本

Class文件的头4个字节称之为魔数(Magic Number),用于确认这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数值为:0xCAFEBABE(咖啡宝贝?),这个魔数在Java还是“Oak”的时候就被确认了。
之后4个字节存储的是Class文件的版本号,第5第6个字节是次版本号(Minor Version),第7第8是主版本号(Major Version)。Java的版本号是从45开始的,1.1之后大版本发布主版本号向上加1,高版本JDK能向下兼容但不能运行以后版本的Class文件。

常量池

主次版本号之后时常量池,Class文件之中的资源仓库,关联其他项目最多的数据类型,也是占用Class文件空间最大的项目之一。由于常量数量不固定,所以常量池的入口需要防止一项u2类型数据,代表常量池容量计数值(constant_pool_count)。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近与Java语言层面的常量概念,如文本字符串、声明为final的常量值等。符号引用则属于编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称与描述符

Java代码在Javac编译的时候,不像C或C++有连接这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。虚拟机运行时,从常量池获得对应的符号引用,在类创建时或运行时解析、翻译到具体的内存地址之中。

访问标志

常量池之后紧接着两个字节代表访问标志(access_flag),用于识别一些类或者接口层次的访问信息。包括:是类还是接口,是否public,是否abstract,是否final等。

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public
ACC_FINAL 0x0010 是否为final
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语意,由于该语意在JDK1.0.2发生过改变,所以之后编译出来的类这个标志必须为真
ACC_INTERFACE 0x0200 是否为接口
ACC_ABSTRACT 0x0400 是否为抽象类
ACC_SYNTHETIC 0x1000 是否由编译器自动产生的
ACC_ANNOTATION 0x2000 是否为注解类
ACC_ENUM 0x4000 是否为enum

例如public的普通类,所以ACC_PUBLIC ACC_SUPE标志为真,其他标志为假。因此access_flags的值为: 0x0001|0x0020=0x0021
前5个是Java虚拟机规范定义标志,1.5之后增加了后面的三种。

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

类索引(this_class)父类索引(super_class)是u2类型数据,接口索引集合(interfaces)时一组u2类型的数据集合。按照顺序排在访问标志之后。

类型 名称 数量
u2 类索引 1
u2 父类索引 1
u2[n] 接口索引 n

字段表集合

字段表(field_info)用于描述接口或者类中声明的变量。字段包括类级变量以及实例级别变量,但不包括方法内部声明的局部变量。修饰符使用标志量表示,字段名称类型使用常量池中的常量来描述。
access_flag的含义

标志名称 标志值 含义
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 是否为enum
类型 名称 数量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

方法表集合

方法表(method_info)用于描述接口或者类中声明的方法。
因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有了 ACC_VOLATILE标志和ACC_TRANSIENT标志。与之相对的,synchronized、native、strictfp 和abstract关键字可以修饰方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZED、 ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。

标志名称 标志值 含义
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 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

属性表集合

属性表(attribute_info)之前反复出现,在Class文件、字段表、方法表都可以携带字节的属性表集合,用于描述某些场景专有信息。
顺序、长度、内容的要求不像前面那么严格。
不要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人 实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不 认识的属性。

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类才有这个属性,用于标识这个类所在的外围方法

...

属性表结构:

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

完整结构描述

表用于描述有层次关系的符合结构的数据,整个Class文件本质上就是一张表,其构成成分就是如下的数据项:

数据类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attribute_count 1
attribute_info attributes attribute_count

实例

为了能够更好的理解,我们拿一个实际的类文件来分析一下。

源码

package com.software5000.base.jsql;

public class TestClass {
    public String strField;

    public int intField;

    public String getStrField() {
        return strField;
    }

    public void setStrField(String strField) {
        this.strField = strField;
    }

    public int getIntField() {
        return intField;
    }

    public void setIntField(int intField) {
        this.intField = intField;
    }
}

这是一个很简单的测试类,两个属性,以及对应的getter/setter方法。

Class文件

Java虚拟机-类文件结构
类文件结构
实例
总结

分析

针对类文件以及前面的概念对比分析

魔数、Class版本

CAFEBABE - 魔数
00000034 - 版本 52 :jdk 1.8

常量池长度

0020 - 32-1 长度31个常量项

常量池内容

01——
0A tag表示 constant_methodref_info
0005 index no. 5 contant_class_info
001B index no. 27 constant_nameandtype

02——
09 tag constant_fieldref_info
0004 index no.4 constant_class_info
001C index no.28 constatn_nameandtype

03——
09 tag constant_fieldref_info
0004 index no.4 constant_class_info
001D index no.29 constatn_nameandtype

04——
07 tag constant_class_info
001E index no.30

05——
07 tag constant_class_info
001F index no.31

06——
01 tag constant_utf8_info
0008 length 8 byte
7374 7246 6965 6C64 strField

07——
01 tag constant_utf8_info
0012 length 18 byte
4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B Ljava/lang/String;

08——
01 tag constant_utf8_info
0008 length 8 byte
696E 7446 6965 6C64 intField

09——
01 tag constant_utf8_info
0001 length 1 byte
49 I

10——
01 tag constant_utf8_info
0006 length 6 byte
3C69 6E69 743E

11——
01 tag constant_utf8_info
0003 length 3 byte
2829 56 ()V

12——
01 tag constant_utf8_info
0004 length 4 byte
436F 6465 Code

13——
01 tag constant_utf8_info
000F length 15 byte
4C69 6E65 4E75 6D62 6572 5461 626C 65 LineNumberTable

14——
01 tag constant_utf8_info
0012 length 18 byte
4C6F 6361 6C56 6172 6961 626C 6554 6162 6C65 LocalVariableTable

15——
01 tag constant_utf8_info
0004 length 4 byte
7468 6973 this

16——
01 tag constant_utf8_info
0026 length 38 byte
4C63 6F6D 2F73 6F66 7477 6172 6535 3030 302F 6261 7365 2F6A 7371 6C2F 5465 7374 436C 6173 733B Lcom/software5000/base/jsql/TestClass;

17——
01 tag constant_utf8_info
000B length 11 byte
67 6574 5374 7246 6965 6C64 getStrField

18——
01 tag constant_utf8_info
0014 length 20 byte
2829 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B ()Ljava/lang/String;

19——
01 tag constant_utf8_info
000B length 11 byte
7365 7453 7472 4669 656C 64 setStrField

20——
01 tag constant_utf8_info
0015 length 21 byte
284C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B29 56 (Ljava/lang/String;)V

21——
01 tag constant_utf8_info
000B length 11 byte
6765 7449 6E74 4669 656C 64 getIntField

22——
01 tag constant_utf8_info
0003 length 3 byte
2829 49 ()I

23——
01 tag constant_utf8_info
000B length 11 byte
7365 7449 6E74 4669 656C 64 setIntField

24——
01 tag constant_utf8_info
0004 length 4 byte
2849 2956 (I)V

25——
01 tag constant_utf8_info
000A length 10 byte
536F 7572 6365 4669 6C65 SourceFile

26——
01 tag constant_utf8_info
000E length 14 byte
5465 7374 436C 6173 732E 6A61 7661 TestClass.java

27——
0C tag constant_Name-andtype-info
000A index no.10
000B index no.11

28——
0C tag constant_Name-andtype-info
0006 index no.6
0007 index no.7

29——
0C tag constant_Name-andtype-info
0008 index no.8
0009 index no.9

30——
01 tag constant_utf8_info
0024 length 36 byte
5636F 6D2F 736F 6674 7761 7265 3530 3030 2F62 6173 652F 6A73 716C 2F54 6573 7443 6C61 7373 com/software5000/base/jsql/TestClass

31——
01 tag constant_utf8_info
0010 length 16 byte
6A61 7661 2F6C 616E 672F 4F62 6A65 6374 java/lang/Object

访问标志

0021 访问标志 (ACC_PUBLIC,ACC_SUPER两个标志位为真)

类索引、父类索引、接口索引

0004 类索引 no.4
0005 父类索引 no.5
0000 接口索引集合 0

字段表集合

0002 fields_counts 2个字段表数据

No.1 field
0001 access_flag public
0006 index no.6 name_index
0007 index no.7 descriptor_index
0000 attributes_count

No.2 field
0001 access_flag public
0008 index no.8 name_index
0009 index no.9 descriptor_index
0000 attributes_count

方法表集合

0005 methods_count 5个方法

No.1 method
0001 access_flag public
000A index no.10 name_index
000B index no.11 descriptor_index ()V
0001 attributes_count 1个
000C attributes_info LineNumberTable

剩余的待进一步补充。。。

总结

Class文件的结构理解,说实话对于编码或者性能优化等方面没有什么特别大的帮助,但是能够帮我们更好的理解Java及其设计的思想。我们从Class文件的结构设计中也能够学习到一些模式,这些东西可能会在后续的研发过程提供一些解决问题或者设计方案的思路。
这才是最重要的。