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_PUBLIC0x0001是否为public
ACC_FINAL0x0010是否为final
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语意,由于该语意在JDK1.0.2发生过改变,所以之后编译出来的类这个标志必须为真
ACC_INTERFACE0x0200是否为接口
ACC_ABSTRACT0x0400是否为抽象类
ACC_SYNTHETIC0x1000是否由编译器自动产生的
ACC_ANNOTATION0x2000是否为注解类
ACC_ENUM0x4000是否为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_PUBLIC0x0001是否为public
ACC_PRIVATE0x0002是否为private
ACC_PROTECTED0x0004是否为protected
ACC_STATIC0x0008是否为static
ACC_FINAL0x0010是否为final
ACC_VOLATILE0x0040是否为volatile
ACC_TRANSIENT0x0080是否为transient
ACC_SYNTHETIC0x1000是否由编译器自动产生的
ACC_ENUM0x4000是否为enum
类型名称数量
u2access_flag1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

方法表集合

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

标志名称标志值含义
ACC_PUBLIC0x0001是否为public
ACC_PRIVATE0x0002是否为private
ACC_PROTECTED0x0004是否为protected
ACC_STATIC0x0008是否为static
ACC_FINAL0x0010是否为final
ACC_SYNCHRONIZED0x0020是否为synchronized
ACC_BRIDGE0x0040是否为编译器产生的桥接方法
ACC_VARARGS0x0080是否接受不定参数
ACC_NATIVE0x0100是否为native
ACC_ABSTRACT0x0400是否为abstract
ACC_STRICTFP0x0800是否为strictfp
ACC_SYNTHETIC0x1000是否由编译器自动产生的
类型名称数量
u2access_flag1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

属性表集合

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

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

属性表结构:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

完整结构描述

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

数据类型名称数量
u2attribute_name_index1
u4attribute_length1
u2max_stack1
u2max_locals1
u4code_length1
u1codecode_length
u2exception_table_length1
exception_infoexception_tableexception_table_length
u2attribute_count1
attribute_infoattributesattribute_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文件

Class文件

分析

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

魔数、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文件的结构设计中也能够学习到一些模式,这些东西可能会在后续的研发过程提供一些解决问题或者设计方案的思路。
这才是最重要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值