来自JVM的一封ClassFile介绍信

我是一个class文件。我的内部是由一个被叫做ClassFile的structure组成。

我在jvm中占有很重要的地位,你可去看看jvm规范中我占了多少篇幅,告诉你,足足有大半本书!第四章,标题叫做 The class File Format。可以这么说。你把编译器、字节码、jit那些看过以后,再把我搞清楚,基本上jvm你也就精通了。鉴于我这么重要,今天我介绍下自己。我长下面这样:

ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }

你也许对这个结构有点疑问,怎么长得这么像c语言的结构体。没错,我们这里就是采用类似c语言结构体的伪结构来表示一个class file的结构。为了说明内部的细节,我们使用了这个伪结构。其实我真实的结构是一组以8位字节为基础单位的二进制流,包含多个数据项,各个数据项严格按照顺序紧凑的排列在Class文件中。其实封面才是我们本来的面目。

为了更友好的说明内部细节,我们还是回到上面那个伪结构,看了这个结构,你也许在纳闷这些u2、u4都是什么鬼。放在一个属性名的前面,有点像类型。没错,基本就是这个意思。在我们classfile的世界上,我们使用一组专用的数据类型来表示class文件的内容。比如u1就代表1个字节的无符号数(unsigned quantity)。u2、u4同理。

在java se platform中,这种类型可以通过java.io.DataInput这个接口中的方法readUnsignedByte,readUnsignedShort, and readInt来读取。

这会你也许又会问那夹杂在u2和u4中间的那些cp_info、field_info以及attribute_info又是什么鬼。其实这个你可以想象下在.java文件中的entity。

什么userinfo啦,orderinfo啦。这里我们把这个结构叫做table。也就是类似数据库里的表结构的样子。嗯,没错。在classfile的世界里,有两种类型,一种就是之前说的那种无符号数,另外一种就是这里的table类型。你再看看这些名字,也就知道大概存储什么东西啦。一个class file结构。肯定得存储这个类有哪些字段吧,那么就存储到field_info这个表里。肯定得存储有哪些方法吧,那么就存储到method_info这个表里。这里边有个cp_info这个是存储什么呢?这个是一个常量池。这个里边存储了方法名、类名、接口名以及源码中那些真正的常量。

其它的项涉及到这些 名称都是存一个常量池的一个索引值。也就是一个引用。

这会你也许又要问。uX和*_info后面那些类似字段名称的东西是什么意思呢。为了避免与类的字段和类的实例等概念产生混淆,这里我们给这些类似字段的东东起了一个新的名词叫:项。英文叫item。没错,再读一遍:item。(ps:好像实在想不起起什么名字,最后都叫做item。就像中文一样,不知道叫什么的时候,就叫“那个东西”、“那个人”)。。。。。起名字真的真的不容易。洪荒之力啊。回归正题!

magic (魔数)

先来来看magic是什么“东西”?在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受。看起来他唯一的作用也就是jvm用来放行的门禁卡。这个number的值固定就是0xCAFEBABE。。。。。。。。。

minor_version、major_version

好,现在来看看minor_version和major_version。也许你一看就知道了。这两个是class文件的格式版本号。和我们现在说的版本管理基本一个概念。

major就是大版本。minor就是小版本。比如2.1。major_version就是2。minor_version就是1。

constant_pool_count

在版本号之后,就是constant_pool_count。这个是常量池计数器。它的值等于constant_pool中的成员数加1。

constant_pool[constant_pool_count-1]

那么constant_pool前面已经说到了一部分。这里边包含了class文件结构和其子结构中引用的所有字符串常量,以及类名、接口名、字段名和其他常量。

access_flags

access_flags则表示访问标志。下表是class文件的访问标志们:

Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_FINAL 0x0010 Declared final; no subclasses allowed. ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. ACC_INTERFACE 0x0200 Is an interface, not a class. ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. ACC_ANNOTATION 0x2000 Declared as an annotation type. ACC_ENUM 0x4000 Declared as an enum type.

  • ACC_INTERFACE

带有ACC_INTERFACE标志的class文件表示是一个接口,而不是一个类,如果没有这个标志的则表示是一个类而不是接口。这里你也许会纳闷,奇怪了,访问标志怎么还会混入一些class的类型内容,这个应该再新增一个项啊。比如叫item_type。不过既然标准就是这么定义的。我们知道就是了。

另外如果一个class文件被设置了ACC_INTERFACE,那么同时也要被设置ACC_ABSTRACT标志。同时就不能再去设置ACC_FINAL、ACC_SUPER或ACC_ENUM了。这个也很好理解,就是互斥。

如果没有设置ACC_INTERFACE标志,那么这个class文件可以具有上面这个表中除了ACC_ANNOTATION外的其它所有的标志。

但要要注意的是,ACC_ABSTRACT和ACC_FINAL是互斥的,你懂的,既然抽象了就必须被用来继承,如果再加个final,就没法被继承了。所以是矛盾的,互斥的。

  • ACC_SUPER

你也许看到了一个奇葩的ACC_SUPER。是不是以你的编程经验怎么都猜不出来这个是干嘛的?没错,有这种感觉就对了。因为这个标志是一种为了弥补jvm设计上的一些过错而增加的。说说这个故事吧。在jdk1.02之前,有个叫invokenonvirtual的指令。在1.02后,这个指令被改名叫做invokespecial。invokenonvirtual的时候没有invokespecial那样只允许调用superclass、private方法或<init>方法。于是在所有的1.02后的class 都必须设置ACC_SUPER这个标志,来表明强加给invokespecial的新的约束必须要被遵守。在1.02 之前ACC_SUPER是没什么鸟用的,甚至就没有ACC_SUPER这个标志。 一般情况下, 调用构造方法或者使用super关键字显示调用父类的方法时, 会使用这条字节码指令。 这正是ACC_SUPER这个名字的由来。 在java 1.2之前, invokespecial对方法的调用都是静态绑定的, 而ACC_SUPER这个标志位在java 1.2的时候加入到class文件中, 它为invokespecial这条指令增加了动态绑定的功能。

  • ACC_SYNTHETIC

好,现在再来说说ACC_SYNTHETIC,此标志意味着该类或接口是由编译器生成的,而不是由源码生成的。

  • ACC_ANNOTATION

另外ACC_ANNOTATION也很简单。annotation类型必须设置这个值。如果设置了ACC_ANNOTATION,那么也必须设置ACC_INTERFACE标志。

  • ACC_ENUM

ACC_ENUM标志标明该类或其父类为枚举类型。

access_flags终于搞清楚了。现在来看看

this_class这个项。这个项的值必须是上面的那个常量池中某一项的有效索引值。常量池在这个索引处的成员必须是CONSTANT_Class_info结构。来表示这个class 文件中所定义的类或接口。

super_class(父类索引)

表示这个class文件所定义的类的直接父类。也是上面的常量池某一项的有效索引值。而且结构也必须是CONSTANT_Class_info结构。

interfaces_count (接口计数器)

这个项的值表示当前类或接口的直接超接口的数量。

interfaces[] (接口表)

就是这个类所实现的接口。里边同样是常量池的索引值。接口表里边的顺序和源代码的接口顺序是一致的。

fields_count (字段计数器)

fields_count项的值是当前类文件fields表中field_info结构体的数量。

fields[] (字段表)

在fields中的每个值都必须是field_info结构。表中的每个值都完整的描述了该类或接口中的一个字段。fields表中只包含当前类或接口中的字段,不包含超类或super 接口中的字段。

methods_count (方法计数器) methods_count项的值是当前类文件方法表中method_info结构体的数量。

也就是类文件中方法的数量。

methods[] (方法表)

methods表中的每个成员都必须是一个method_info结构,用来表示当前类或接口中的某个方法的完整描述。

attributes_count (属性计数器) attributes_count的值是该类中的attributes表中属性的数量

attributes[] (属性表)

同样的上面的各种表一样。里边的每个值都必须是attribute_info结构。这个属性也许你理解起来有点抽象,不知道是什么。这时候你可以想想一个类文件结构那么复杂。比如一个方法中那么多代码是怎么表示的呢?其实这些事情就是由jvm中预定义好的属性来表示的。 jvm中有很多的预定义属性。其实也是一个结构。类似今天class file的结构。你可以这样认为,一个类类或接口这么复杂。就像我们做一个应用一样,需要设计很多数据表来实现数据存储。你可以把这些预定义属性理解为jvm中的数据表。有关属性的内容我们以后会专门抽出一期来讲解这方面的内容。

好了,鉴于公号文章篇幅限制,况且你也一下子看不完几十页,本文就介绍这么多。希望通过这封介绍信能够让你了解我!了解JVM!作为吃瓜群众,今天还有更重要的事情要做!

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2016-08-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏nnngu

百度搜索 “Java面试题” 前200页(面试必看)

本文中的题目来源于网上的一篇文章《百度搜索 “Java面试题” 前200页》,但该文章里面只有题目,没有答案。因此,我整理了一些答案发布于本文。本文整理答案的原...

81711
来自专栏酷玩时刻

微信扫码支付(模式一)微信扫码支付(模式一)

官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4

2193
来自专栏点滴积累

geotrellis使用(三十)使用geotrellis读取PostGIS空间数据

前言 最近事情很多,各种你想不到的事情——such as singing and dancing——再加上最近又研究docker上瘾,所以geotrellis看...

4327
来自专栏Jerry的SAP技术分享

程序员面试系列之Java单例模式的攻击与防御

Java程序员面试系列-什么是Java Marker Interface(标记接口)

1162
来自专栏代码世界

Python之几种常用模块

模块 注意事项: 所有的模块导入都应该尽量往上写 内置模块 扩展模块 自定义模块 模块不会重复被导入 : sys.moudles 从哪儿...

3466
来自专栏SDNLAB

《P4语言规范》parser详解

前言 为深入研究P4语言相关规范及运行操作使用,本系列文章根据P4.org网站给出的《The P4 Language Specification v1.0.2》...

4069
来自专栏熊二哥

单例模式深入理解

最近去平安系面试时,遇到了个人技术领域认定的一大偶像吴大师(Cat作者),他随口问了个单例的问题,要求基于Java技术栈,给出几种单例的方案,并给出单元测试代码...

23310
来自专栏瓜大三哥

Verilog代码设计风格

1.信号命名规则 信号命名规则在团队开发中占据着重要地位,统一、有序的命名能大幅减少设计人员之间的冗余工作,还可便于团队成员代码的查错和验证。比较著名的信号命名...

2217
来自专栏IT米粉

你必须了解的反射——反射来实现实体验证

日常开发,都是通过API进行前后端的系统对接,对API参数的验证是一个使用率非常高的功能,如果能非常简便的的进行参数验证,能降低代码量,提升工作效率。

4078
来自专栏Spark学习技巧

Hive : SORT BY vs ORDER BY vs DISTRIBUTE BY vs CLUSTER BY

在Apache Hive中,像SQL一样,您可以根据全局排序和分布要求决定对数据进行全局排序或局部排序。在这篇文章中,我们将了解Hive中的SORT BY,OR...

3245

扫码关注云+社区

领取腾讯云代金券