我们在学习java的时候总是会说java是一种跨平台性的语言,即一次编译,到处运行。但是为什么说java能够一次编译,导出运行呢?其实这主要还是需要依赖jvm,而JVM其实是不认识java文件的,因此当java程序被编译之后,会变成class文件,只要相应的class文件能够符合jvm的规范要求,那他们就能够被jvm所运行。因此现在除了java语言之外,kotlin,Scala,Clojure等语言都可以在jvm上运行。可以说,很多语言的出现都离不开JVM。那么对于class文件的基本结构,又该如何深入学习呢?
首先在本次学习class文件结构之前,我们需要先准备两个东西。
1.HelloWorld.java程序:
2.OoLong.jar。Oolong这是一种汇编语言的名称。首先我们需要到指定的github上边去下载资源。
https://github.com/jpbirdy/programming-for-the-jvm
下载完毕之后,需要将相应的文件编译成class字节码,然后在用java命令将其打包成jar文件。
直接进入src目录底下,输入以下命令:
jar cvf OoLong.jar COM
(这里的COM文件夹底下的所有java程序都已经被编译成了class字节码)
然后COM目录底下的内容就会被自动打包成为一个叫做OoLang.jar的工具包了。
然后我们将该jar包放在需要测试的Helloworld.class字节码文件同级目录处。
java -verbose -cp OoLong.jar COM.sootNsmoke.oolong.Gnoloo HelloWorld.class
输入以上命令,即可进行转换:
转换完毕之后,大致如下图所示:
接着小编用Notepad++打开了HelloWorld.j文件,查看了里面的内容:
稍微来解释一下:
.source 指令是指程序的源文件
.class 表示这是一个类 并指定相应的类名
.super 表示这个类的父类是Object类型
.method public ()V 表示这是一个公有的方法 表示这是一个构造函数 V表示该函数的返回类型为void好了,接下来我们将这个HelloWorld.java文件转换成二进制的字节码文件进行试验:
为了方便阅读和下边的讲解,我用了下边这个指令来进行测试:
java -verbose -cp OoLong.jar COM.sootNsmoke.oolong.DumpClass HelloWorld.class
文件被转成了二进制的字节码文件后,内容如下图所示:
(为了便于观察,小编在实验的时候使用了idea编辑器的终端窗口)
现在我们来对这段二进制文件作深入的分析。
首先是文件的头部信息:
1.魔数
这段头部信息也被称之为魔数。主要是用来标识该class文件的类型。通常都是以cafebabe这段十六进制的数字来表示。
2.Class文件的版本信息
接着魔数的下方,我们会发现这些内容:
这里的两个字节分别是表示相应的最大最小java版本范围。
总结起来就是从000000-000006的这6个字节分别表示了这个class文件的基本头部信息。
这些数字的作用主要是限制低版本jvm执行高版本的class文件。这也就是为什么有的时候,高版本jvm环境生成的class文件在移植到低版本jvm环境中无法执行的根本原因了。3.常量池
接下来的这部分主要是描述常量池的内容:
这些东西被称之为常量池,常量池里面主要存放了两种类型的常量。
字面值常量(String字符串和final修饰的值)和符号引用(类名,接口名字,方法名称,字段名称等)
仔细观察会发现,每个常量都是由三个字节来进行描述的。基本结构如下:
jvm中定义了多种常量的类型,分别用数字来一一对应。
常量类型中包括了我们所熟知的一些Integer,String类型。
接下来我们通过解释几个不常见的常量类型来加深对于常量池的理解:
UTF8常量类型(有些书籍也称之为CONSTANT_Utf8_info)
在jvm的数字对应规则中,01表示的常量类型是utf8常量,这里的末尾数字6表示这个常量的长度为6。
在CONSTANT_Utf8_info常量类型里面,该常量的长度被限制为了2个字节,也就是64k,又由于class文件的接口,字段,方法名称,包名都是用这个常量类来表示的,因此类的全限定名、字段名、方法名的最大长度不能超过2个字节所能表示的最大整数,也就是65535。
Fieldref,Methodref常量类型
这种类型主要是用于描述class项里面的属性和方法。
Class常量类型
这种常量类型主要是用于描述该类的名称
4.访问标志
在常量池之后的部分,主要是一些描述类本身的相关信息内容。例如是否实现接口,是否为抽象类,属性个数,方法个数等。
5.Fields和Methods部分
这部分的内容主要是指程序本身的属性和方法的一些描述信息:
例如说一些本地栈的深度,常量的个数等信息,以及异常个数等信息:
6.class属性的描述
这里面主要是两个java语言的属性描述,sourcefile和name的描述。