类文件结构

前言

本文是《深入理解Java虚拟机》第6章的部分知识点,这一章正如作者所说,对数据结构的讲解确实枯燥,对于失眠治疗真的是非常有效,本人经常看着看着就睡着了。因为内容比较多,所以本人对部分章节就浅尝辄止了(水一水)。本文的目标是能让读者对类文件结构与字节码指令有一个大概的了解,想要了解更多,读者可以查阅原文。

本章知识点

  • 类文件的结构
  • 字节码指令

类文件的结构

Class文件是一组以8位字节为基础单位的二进制流,各项数据严格按照顺序紧凑地排列且中间没有任何分隔符,当遇到数据项需要占用8位字节以上空间时,会按照高位在前的方式分割成若干个8位字节进行存储。
Class数据结构以一种伪结构来存储数据,该伪结构中只有两种数据类型:无符号数无符号数指的是基本数据类型,以u1、u2、u4、u8来代表1个字节、2个字节、4个字节、八个字节的无符号数,它可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。由多个无符号数或者其他表作为数据项构成的复合数据类型,他们的特征是都以_info结尾。
Class文件的格式如下表所示:

类型 英文名 中文名 数量
u4 magic 魔数 1
u2 minor_version 次版本号 1
u2 major_version 主版本号 1
u2 constant_pool_count 常量计数器 1
cp_info constant_pool 常量池 constant_pool_count-1
u2 access_flags 访问标志 1
u2 this_class 类索引 1
u2 super_class 父类索引 1
u2 interfaces_count 接口计数器 1
u2 interfaces 接口索引 interfaces_count
u2 fields_count 字段计数器 1
field_info fields 字段表 fields_count
u2 methods_count 方法计数器 1
method_info methods 方法表 methods_count
u2 attributes_count 属性计数器 1
attribute_info attributes 属性表 attributes_count

下面,我们一起来看看Class文件格式中各数据项的具体含义。

魔数

魔数的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数值为0xCAFEBABE,可以记作"咖啡宝贝"。

版本号

版本号用于判断Class文件的版本是否满足当前JDK版本。高版本的JDK可以向下兼容低版本的Class文件,但不能向上兼容超过其版本号的Class文件。

常量池

由于常量池中常量的数量是不固定的,所以需要使用常量计数器来标识常量的数量。该计数器是从1开始而不是0,目的是在于满足后面某些指向常量池的索引值在特定情况下需要表达“不使用任何一个常量池项目”的含义。
常量池中存放两类常量:字面量符号引用字面量包含了文本字符串、声明为final的常量值等。符号引用包含了类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

访问标志

访问标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否是public类型;是否是abstract类型;如果是类,是否被声明为final等。

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

类索引确定这个类的全限定名,父类索引确定这个类的父类的全限定名,因为Java不允许多继承,所有父类索引只有一个(只有java.lang.Object的父类索引为0)。接口索引集合用于描述这个类实现了哪些接口,这些被实现的接口在集合中按照源码内implements后的顺序从左到右排序。

字段表集合

字段表用于描述接口或者类中声明的变量(不包括方法内部的局部变量)。它包含了字段的修饰符信息,各修饰符都使用布尔值来代表是否存在,至于字段叫什么名字以及被定义为什么数据类型,因为长度无法固定,所以引用常量池中的常量来描述。

方法表集合

方法表用于描述类中的方法,它包括了方法的修饰符信息,各修饰符都使用布尔值来代表是否存在,至于方法叫什么名字以及返回什么数据类型,因为长度无法固定,所以引用常量池中的常量来描述。至于方法中的代码,会经过编译器编译成字节码指令后,存放到方法属性表集合中一个名为Code的属性中。

属性表集合

属性表集合存在Class文件、字段表、方法表中,用于描述某些场景专有的信息。与Class文件其他数据项不同,属性表集合不再要求各个属性表具有严格顺序,只要不与已有属性重复即可。属性表集合预定义了许多种属性名称,如:Code、ConstantValue、Exceptions等。在这里,便不对这些属性名称进行展开了,读者可以翻看《深入理解Java虚拟机》了解各属性的含义。

字节码指令

字节码指令由操作码(1个字节长度、有特定含义的数字)和操作数(0个或多个所需参数)构成。

字节码与数据类型

对于大多数字节码指令,它们的操作码助记符中都有特殊的字符来表明其为哪种数据类型服务,如下表所示:

类型 字符
int i
long l
short s
byte b
char c
float f
double d
reference a

也有一些操作码助记符没有代表数据类型的特殊字符,如arraylength指令,goto指令等。需要注意的一点是,大多数对于boolean、byte、short和char类型数据的操作,实际上都是使用相应的int类型作为运算类型

加载和存储指令

加载和和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。它包含如下几类指令:

  • 将一个局部变量加载到操作栈:iload, iload_<n>, etc.
  • 将一个数值从操作数栈存储到局部变量表:istore, istore_<n>, etc.
  • 将一个常量加载到操作数栈:bipush, sipush, ldc, etc.
  • 扩充局部变量表的访问索引:wide

运算指令

运算指令用于对两个操作数栈上的值进行运算,并把结果存入到操作栈顶。它包含如下几类指令:

  • 加法指令:iad, ladd, fadd, dadd
  • 减法指令:isub, lsub, fsub, dsub
  • 乘法指令:imul, lmul, fmul, duml
  • 除法指令:idiv, ldiv, fdiv, ddiv
  • 求余指令:irem, lrem, frem, drem
  • 取反指令:ineg, lneg, fneg, dneg
  • 位移指令:ishl, ishr, iushr, lshl, lshr, lushr
  • 按位或指令:ior, lor
  • 按位与指令:iand, land
  • 按位异或指令:ixor, lxor
  • 局部变量自增指令:iinc
  • 比较指令:dcmpg, dcmpl, fcmpg, fcmpl, lcmp

类型转换指令

类型转换指令可以将两种不同的数值类型进行相互转换,JVM直接支持小范围类型向大范围类型的安全转换,相对的,处理大范围类型到小范围类型的窄化类型转换,则需要显示地使用转换指令来完成,这些指令包括:i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l和d2f,需要注意的是,窄化类型转换会导致结果产生不同的正负号、不同的数量级、数值精度丢失的情况。

对象创建与访问指令

类实例与数组都属于对象,但是其创建与操作使用了不同的字节码指令,指令如下:

  • 创建类实例:new
  • 创建数组:newarray, anewarray, multianewarray
  • 访问类字段(static字段)和实例字段:getfield, putfield, getstatic, putstatic
  • 把一个数组元素加载到操作数栈:baload, caload, saload, iaload, laload, faload, etc.
  • 将一个操作数栈的值存储到数组元素中:bastore, castore, sastore, iastore, etc.
  • 取数组长度:arraylength
  • 检查类实例类型:instanceof, checkcast

操作数栈管理指令

操作数栈管理指令用于直接操作操作数栈,指令如下:

  • 将操作数栈的栈顶1个或2个元素出栈:pop, pop2
  • 复制栈顶1个或2个数值重新压入栈顶:dup, dup2, dup_x1, dup2_x1, etc.
  • 将栈最顶端的两个数值互换:swap

控制转移指令

控制转移指令可以让JVM有条件或无条件地执行指定位置指令,指令如下:

  • 条件分支:ifeq, iflt, ifle, ifne, ifgt, ifge, etc.
  • 复合条件分支:tableswitch, lookupswitch
  • 无条件分支:goto, goto_w, jsr, jsr_w, ret

方法调用和返回指令

方法调用指令有如下:

  • invokevirtual:用于调用对象的实例方法
  • invokeinterface:用于调用接口方法
  • invokespecial:用于调用一些特殊处理的实例方法,包括实例初始化方法,私有方法和父类方法
  • invokestatic:用于调用类方法
  • invokedynamic:用于在运行时动态解析出调用点限定符所引用的方法(非固化在JVM内部的方法)

返回指令是根据返回值的类型区分的,包括return(声明void方法), ireturn, lreturn, freturn, dreturn, areturn.

异常处理指令

在Java程序中显式抛出异常的操作都由athrow指令来实现,至于处理异常,则不是由字节码指令来实现。

同步指令

JVM指令集中有monitorenter(开始同步)和monitorexit(退出同步)两条指令来支持synchronized关键字的语义。

总结

类文件由一组8位字节为基础单位的二进制流按特定的数据项顺序组成,中间没有分隔符。其中各数据项由无符号数组成。数据项的顺序为:魔数(咖啡宝贝)、版本号(主+次)、常量池(字面量+符号引用)、访问标志(标识类或接口)、类索引(类的全限定名)、父类索引(父类的全限定名)、接口索引集合(接口集合的全限定名)、字段表集合(字段信息)、方法表集合(方法信息)、属性表集合(类文件、字段表、方法表中的专有信息)。
字节码指令由操作码(1个字节长度、有特定含义的数字)和操作数(0个或多个所需参数)构成。指令根据类型可以分为:加载和存储指令(数据在局部变量表和操作数栈之间传输)、运算指令(操作数栈上的值运算)、类型转换指令(安全转换与窄化转换)、对象创建与访问指令(数组和对象)、操作数栈管理指令(压栈出栈)、控制转移指令(跳转)、方法调用和返回指令异常处理指令同步指令(开始/退出同步)。

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