《深入理解Java 虚拟机》之类文件结构

JVM多语言支持

Java规范分为Java语言规范(The Java Language Specification)和Java虚拟机规范(The Java Virtual Machine Specification),因此JVM支持多种语言,只要该语言编译后的类文件符合JVM规范。比如我们常用的Scala、Kotlin、Clojure、Groovy等等。

类文件结构

基础原则:多字节的数据,高位在前。JVM加载Class文件的时候进行动态连接。
Class文件结构类似C的结构体,包含无符号数(u1/u2/u4/u8表示1/2/4/8字节的无符号数)和表(由多个无符号数或表组成的结构体,class文件本身就是一个大的表),有多个同类的无符号数或者表并数量不确定的时候,一般先用一个无符号数记录数量,后面接上一系列连续的这种无符号数或者表。Class文件没有分隔符号,所以整个数据结构都是被严格规定的。


image

魔数与Class文件版本

Class文件头4个字节是固定的0xCAFEBABE(咖啡宝贝),显然与Java语言命名的历史相关。
紧接着4个字节存储Class文件版本号,5-6字节是子版本号,7-8字节是主版本号(比如1.7.0是0x0033)。JVM读取Class文件的时候,搞版本JDK可以兼容旧版本Class文件,就是通过这4个字节进行判定的。

常量池

一般来说常量池占Class文件空间最大,由于长度不定,所以入口有u2类型的常量池容量计数器(8-9位),计数从1开始(Class文件中其他容量计数器都是从0开始的)。
常量池存储两类常量:字面量(类似Java语言中的常量)和符号引用,后者包括类和接口全名、字段名称和描述符、方法名称和描述符。JVM运行时从常量池获取符号引用再在类创建时解析到具体内存地址,没有C语言的“连接”步骤。
常量池每一个常量都是一个表。,一共有14种表,表的开头都是一个u1类型的标志位代表当前常量的类型(浮点整形之类),后面的结构与具体的常量类型有关,各自不同。
使用javap -verbose 类名可以解析类的结构,输出结构大概这样:

 javap -verbose com.turingdi.breorent.user.controller.RentAndReturnController

Classfile /home/leibniz/workspace/BreoRent/target/classes/com/turingdi/breorent/user/controller/RentAndReturnController.class
  Last modified 2017-6-6; size 9126 bytes
  MD5 checksum 26ed5594f39cfc9d6b109637ad76bf12
  Compiled from "RentAndReturnController.java"
public class com.turingdi.breorent.user.controller.RentAndReturnController
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    #1 = Methodref          #111.#203     // java/lang/Object."<init>":()V
    #2 = Class              #204          // org/springframework/web/servlet/ModelAndView
    #3 = Methodref          #2.#203       // org/springframework/web/servlet/ModelAndView."<init>":()V
    #4 = Class              #205          // com/turingdi/breorent/common/wechatApi/process/WechatJdk
    #5 = Methodref          #4.#206       // com/turingdi/breorent/common/wechatApi/process/WechatJdk."<init>":(Ljavax/servlet/http/HttpServletRequest;)V
    #6 = Methodref          #4.#207       // com/turingdi/breorent/common/wechatApi/process/WechatJdk.getMap:()Ljava/util/Map;
    #7 = String             #208          // appId
    #8 = String             #209          // wechat
    #9 = String             #210          // APP_ID
……

常见的两种常量举例:

  1. CONSTANT_Class_info,类常量,标志为0x07,紧接着是1个u2类型的类名索引,指的是类名(字符串常量)在常量池中的索引(如上面所说,从1开始数)。
  2. CONSTANT_Utf8_info,字符串变量,标志为0x01,紧接着是1个u2类型的字符串长度,然后是字符串内容的bytes,u1类型,数量等于前面u2定义的。

从上面可以推导:类名(全限定名)是字符串常量,长度用u2类型表示,也就是最大长度是65535,换言之就是Java类全名最长65535,超过的无法编译。

访问标志

常量池结束之后,有两个字节为访问标志,代表当前Class文件是否public、是否类/接口/枚举、是否抽象、是否注解等等。

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

类索引父类索引分别为u2类型数据,接口索引集合结合为u2类型数据的集合(只能有一个父类,可以实现多个接口),分别用于记录当前类、父类、实现的接口的类描述符(CONSTANT_Class_info)在常量池中的索引。
类索引和父类索引紧接在访问标志后面,再后面是接口索引集合,入口是一个数量计数器,0表示没有实现任何接口,再后面就是具体的接口类描述符索引。

字段表集合

描述类或接口中定义的字段,包括静态和非静态的。结构如下:

类型 名称 数量 含义
u2 access_flags 1 访问标志,public/private/final/static/enum等描述符
u2 name_index 1 字段简单名称在常量池中的索引,即变量名或方法名
u2 descriptor_index 1 字段和方法的描述符在常量池的索引,描述字段类型或方法参数列表/返回类型
u2 attibutes_count 1 属性表计数器
attribute_info attributes attibutes_count 属性额外描述,比如描述变量初始化值在常量池中的索引

描述符描述方法的时候,先是参数列表,然后是返回值类型。而方法

public String toString(int test)

对应的描述符是

(I)Ljava/lang/String;

其中L是表示对象类型。
此外,字段表集合不会列出从父类或接口中继承的字段,但可能会有代码中不存在的字段,比如内部类对外部类实例的引用之类。

方法表集合

方法表集合的入口同样也是一个u2类型的计数器,紧接着是各个具体的方法。方法表的结构与字段表基本一样,不列出来了。区别:

  1. 首先是access_flags的取值范围不同,比如没有ACC_TRANSIENT、有ACC_SYNCHRONIZED等值;
  2. name_index表示方法名索引,descriptor_index表示方法描述符索引,跟字段表一样;
  3. 而编译后的方法代码,放在属性表里面名为“Code”的属性中;
  4. 没有Override的父类方法,不会出现在子类的方法表集合中;
  5. 同样可能出现代码中原本没有的方法,比如<clinit>(类构造器)、<init>(实例对象构造器)。

两个方法名字相同,参数列表相同,返回值类型不同,是允许共存在一个Class文件中的,但Java语言不允许这样。

属性表集合

Class文件、字段表、方法表都可以有自己的属性表,Java7里面定义了21种属性。

Code属性

并非所有方法表都有Code属性,比如接口和抽象类的方法就没有。结构如下:

类型 名称 数量 含义
u2 attribute_name_index 1 属性名的索引,对Code属性而言恒为”Code”
u4 attribute_length 1 属性值长度,相当于整个属性表长度长度减6(u2+u4)
u2 max_stack 1 操作数栈深度最大值。JVM运行时根据此值分配栈桢的操作栈深度
u2 max_locals 1 局部变量表所需存储空间,单位是Slot,double和long占用2个Slot、其他基本类型1Slot,Slot空间可以重用(变量作用域问题)
u4 code_length 1 编译后的字节码长度,理论上最长2^32-1,实际上JVM规定一个方法不允许超过65535条字节码指令
u1 code code_length 代码编译后的字节码
u2 exception_table_length 1 异常表长度
exception_info exception_table exception_table_length 异常表,记录字节码在start_pc到end_pc行之间如果出现类型为catch_type或其子类的异常则跳转到handler_pc行继续处理
u2 attibutes_count 1 属性表计数器
attribute_info attributes attibutes_count 属性额外描述,比如描述变量初始化值在常量池中的索引

字节码值得注意的一个地方是,javac编译时将this关键字作为一个普通方法参数由JVM调用时自动传入。

Exceptions属性

描述方法可能抛出的受检异常。

LineNumberTable属性

描述Java远吗行号与字节码行号之间映射关系,也就是为什么抛异常的时候可以显示源码哪一行抛出的。

LocalVariableTable属性

描述栈桢中局部变量表与Java源码中变量的关系,以保证编译后的代码被其他代码调用时,IDE可以显示参数名(否则被arg0、arg1之类的变量名代替)

SourceFile属性

描述生成当前Class文件的源文件名称,也是抛异常时可以显示源文件名字的原因。但内部类不会生成这个属性。

ConstantValue属性

static关键字修饰的变量可以使用这个属性。对于Sun javac编译器,final static的变量采用ConstantValue属性初始化,其他static变量在<clinit>(类构造器)中初始化。

InnerClasses属性

记录内部类和宿主类的关联。内部类和宿主类的Class文件都会有这个属性。

Signature属性

记录泛型签名信息。Java的泛型是使用擦除式实现的伪泛型,编译后擦除泛型,这个属性为了弥补此缺陷,方便反射API可以拿到泛型类型。

字节码指令

字节码指令由一个字节的操作码(代表具体操作)及跟随其后的0个或多个操作数(操作所需的参数)组成。JVM大多数指令不含操作数只有操作码。
Class文件放弃了操作数对齐,因此省略很多填充和分割符,因此体积可以尽量小;缺点是损失一些解析字节码的性能。
JVM的指令大多数包含了操作的数据类型信息,但因为只有一个字节,也就是说最多只有256种指令,所以不是所有命令对所有数据类型都有独立的指令(非完全独立),同时提供一些指令将指令不支持类型的操作数转换为可支持的类型。
JVM的浮点数运算,舍入模式是最低有效位向下(0)取整。操作溢出时用有符号的无穷大表示(INF),如果操作结果没有明确数学意义则得到NaN(非数字,比如0/0,∞×0之类)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容