深入理解JVM:class文件结构分析

代码编译的结果就是将我们写的语言代码变为字节码,这是存储格式发展的一小步,确实编译语言的一大步。“字节码文件”是平台无关性的基石。

1.语言无关性概述

实现语言无关性的基础仍然是虚拟机和字节码存储格式。作为一个通用的,和机器无关的执行平台,Java虚拟机不与任何语言绑定,只与“Class文件”这种特定的二进制文件格式关联,也就是不关心Class的来源是何种语言,不管是用Java编译的Class文件,还是用JRuby编译的Class文件,JVM都可以做到执行处理。
Java虚拟机提供的语言无关性

2.Class类文件结构

2.1.Class文件格式

Class文件是一组以8位字节为基础单位的二进制流,各数据项严格按照顺序紧凑安排,整个Class文件中存储的内容几乎全部是程序运行的必要数据,无任何分隔符,没有空隙存在。当遇到占用8位字节以上的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
根据JVM虚拟机规范规定,Class文件格式采用类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表

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

整个Class文件本质上就是一张表,所构成的数据项如下表所示:

类型名称数量说明
u4magic1魔数
u2minor_version1次版本号
u2major_version1主版本号
u2constant_pool_count1常量池计数器
cp_infoconstant_poolcontant_pool_count-1常量池
u2access_flags1访问标志
u2this_class1类索引
u2super_class1父类索引
u2interfaces_count1接口计数器
u2interfacesinterfaces_count接口索引集合
u2fields_count1字段表计数器
field_infofieldsfields_count字段表集合
u2methods_count1方法表计数器
method_infomethodsmethods_count方法表集合
u2attributes_count1属性表计数器
attribute_infoattributesattributes_count属性表集合

下面通过一段Java代码编译成Class文件,比对看看Java Class的文件结构。

Java代码

package com.max.xihu.test;
public class TestClass {
    private int a;
    public int info(){
        return a+1;
    }
}

编译后的class文件内容

cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0469 6e66
6f01 0003 2829 4901 000a 536f 7572 6365
4669 6c65 0100 0e54 6573 7443 6c61 7373
2e6a 6176 610c 0007 0008 0c00 0500 0601
001b 636f 6d2f 6d61 782f 7869 6875 2f74
6573 742f 5465 7374 436c 6173 7301 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
0021 0003 0004 0000 0001 0002 0005 0006
0000 0002 0001 0007 0008 0001 0009 0000
001d 0001 0001 0000 0005 2ab7 0001 b100
0000 0100 0a00 0000 0600 0100 0000 0800
0100 0b00 0c00 0100 0900 0000 1f00 0200
0100 0000 072a b400 0204 60ac 0000 0001
000a 0000 0006 0001 0000 000c 0001 000d
0000 0002 000e 

2.2Class文件格式详解

2.2.1魔数与Class文件版本

1.魔数
每个Class文件的开头都有相同的4个字节成为“魔数”,它的作用就是确定这个文件能被虚拟机所识别。
Class文件的魔数很有意思,0xCAFEBABE(谐音:咖啡宝贝)
2.Class文件版本号
紧接着魔数后面的第5,6字节是次版本号。第7,8字节是主版本号。

2.2.2常量池

紧接着主次版本之后是常量池,常量池可以理解为Class文件中的资源仓库,它是Class文件结构中与其他数据结构关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References
字面量:比较接近Java语言层面的常量概念,如文本字符串,声明为final的常量值等。
符号引用:属于编译原理方面的概念,包括了三类常量:
1)类和接口的全限定名
2)字段的名称和描述符
3)方法的名称和描述符
常量池中每一项常量都是一个表,目前有14种结构各不相同的表结构数据,正因为如此才显得非常繁琐。但这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型。

类型标志描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

2.2.3访问标志

在常量池结束之后,紧接着的两个字节代表的就是访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。具体的标志位及其含义如下表所示。

标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令新语义
ACC_INTERFACE0x0200标识为一个接口
ACC_ABSTRACT0x0400是否为抽象类型
ACC_SYNTHETIC0x1000标识这个类并非是用户代码产生
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

access_flags中一共有16个标志位可以使用,当前只定义了其中8个,没有使用到的标志位一律为0.

2.2.4索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interface)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系

类索引,父类索引和接口索引集合都按照顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。

2.2.5字段表集合

字段表(field_info)用于描述接口或者类中声明的变量。而字段(field)包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。字段访问标志如下表。

标志名称标志值含义
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

关于描述标识字符的含义,这里说明下数组类型:对于数组类型,每一维度将使用一个前置的“["字符来描述,如定义一个“java.lang.String[][]”类型的数组,将被标记为“[[Ljava/lang/String;”一个整型的数组“int[]”将被标记为“[I”。

2.2.6方法表集合

如果对字段表集合熟悉的话,那么理解方法表集合就非常简单了。Class文件存储格式中对方法的描述与对字段的描述符几乎采用了完全一致的方式。但是还是稍微区别的。因为volatile和transient不能修饰方法,故方法表中的访问标志中没有ACC_VOLATILEACC_TRANSIENT,但是方法表中的访问标志增加了
ACC_SYNCHRONIZED,ACC_NATIVE,ACC_STRICTFPACC_ABSTRACT标志。

2.2.7属性表集合

在Class文件,字段表,方法表都可以携带自己的属性表集合,以用来藐视某些场景的专有信息。这里主要介绍一下Code属性与ConstantValue属性。
1.Code属性
Java程序方法体中的代码经过Java编译器处理之后,最终变为字节码指令存储在Code属性内。Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码和元数据两部分,那么在整个Class文件中,Code属性用于描述代码,所有其他的数据项都在用于描述元数据。

1)虚拟机局部变量的分配
所使用的的最小单位是Slot。长度不超过32位的基本数据类型,每个局部变量占用一个Slot,而对于double和long这两种64位的数据类型则需要两个Slot来存放。Slot是可以重用的。当代码行超出一个局部变量的作用域时,这个局部变量所占用的Slot可以被其他局部变量所使用。

2)关于this
在任何实例方法里面,都可以通过“this”关键字访问到此方法所属的对象,是实例方法中的隐藏参数。Javac编译器会把this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。在实例方法的局部变量表中预留的第一个Slot位就是为这个“this”准备的,方法参数值从1开始计算。
2.ConstantValue属性
这个的作用就是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这个属性。比如:int x = 123static int x = 123这两种赋值可能是比较常见的赋值方式,但是对于虚拟机而言,这两种变量的赋值方式和时刻都有所不同。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值