看懂class文件 转

时间:2024-01-04 10:07:08

前言

现在周六公司进行一系列的java培训,刚上来就给我看class文件,比较头疼,不过感觉还是学到了一些东西,毕竟像老大说的,想要变得牛逼,是需要多学习多看的。好了,闲话不多说,我整理了一下思路,记录一下自己的学习过程,以后如果有时间的话,我会每个周日整理自己的笔记。我是菜鸡,大家不喜勿喷啊。

曾经风靡前世界的Wirte once,Run Anywhere,让Java这门语言在编程语言上大放异彩,至今仍然保持着第一受欢迎的地位。虽然Sun公司已经被收购,詹姆斯~高斯林前段时间找工作还被歧视年纪大,但我们还是对这门语言充满信心。好了,吹多了。Write once,Run AnyWhere基础实现就是虚拟机(JVM)和字节码储存格式。当然了,这里我主要还是记录一下字节码格式了,JVM以后有时间再学习吧。

这里的字节码格式,就是我们今天要说的Class文件了。当然了,这种说法如果考究起来还是不那么贴切的。因为任何一个Class文件都对应着唯一一个类和接口的定义信息,但是反过来说吧,类和接口并不一定非得在文件中(因为有些类或者接口可以通过classLoader自动生成)。

魔数 版本号

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符,就像下面的这样:

看懂class文件 转vc+28c+wtcSjrLWryse21MDtveLJz8PmxMfQqcr919a7ucrH09Cw79b6tcShozwvcD4NCjxwPs7et/u6xcr9yvTT2rv5sb61xMr9vt3A4NDNo6zV4sDvy/y2qNLlwct1MaGidTKhonU0oaJ1OMC0tPqx7TG49tfWvdqhojK49tfWvdqhojS49tfWvdq6zTi49tfWvdq1xM7et/u6xcr9oaPL/L/J0tTD6Mr2uty24LarzvejrLHIyOfK/dfWoaLL99L90v3Tw6Giyv3Bv9a1u/LV31VURi04seDC67m5s8m1xNfWt/u0rta1oaM8L3A+DQo8cD66w8HLo6zP1tTav6rKvLfWzvbO0sPHtcRjbGFzc87EvP7By6Os1abDx9K70NDSu9DQwLSjrMbk1tC63LbgwcujrLa8ysdKYXZh0OnE4rv6tcS55re2o6zL+dLUztLDx8HLveLSu8/Cvs3Q0MHLo6zPyL+0tdrSu9DQo7o8L3A+DQo8cD48aW1nIGFsdD0="这里写图片描述" src="/uploadfile/Collfiles/20170612/20170612092423657.png" title="\" />

第一行第一个红框,4个字节,如果看成英语,那么就是cafebabe了,也就是我们的咖啡了,你看java的logo是不是就是个咖啡样子啊?这四个字节在被称之为魔数(Magic Number),唯一的作用不是为了好玩,是为了判断是否成为虚拟机接受的class文件。当然像这种判断方式有很多了,我们经常用的图片的格式并不是以后缀名png,jpg来判断的,而是通过图片的头文件的数据来判断的,哎,这里吐槽一下,刚开始第一家公司写项目的时候,后台就是根据后缀名去剪切图像,经常出现图片不能保存的错误,搞得我么 一度很尴尬啊。

第二个红框,也就是第5个和第6个字节代表的是次版本号(是JDK的版本号,不是你写程序的版本号),看上去都是0啊。

第三个红框,也是00 34 第7和第8个字节,16进制的转成十进制的就是52了,这地方得说一下,它代表的是主板本号(Major Version)。Java的版本号是从45开始的,JDK1.1之后每一个大版本发布主版本号向上+1,高版本的JDK能向下兼容以前的老版本的class文件,但是不能运行以后的class文件,即使文件格式未发生任何变化,虚拟机也必须拒绝执行超过其版本号的class文件。

举个例子啊,我的目前运行的是jdk1.8,版本就是52了,虽然我可以执行jdk1.7生成的class文件,但是jdk1.7的环境运行不了我1.8生成的class文件。下图是我在书上找的,可能比较老,还没有到1.8的内容,打个tag吧,JDK 1.8.0_40的major version是52。

看懂class文件 转

现在还介绍最后一个框00 21的意思了。这个是常量池的入口了。常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其它项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。

常量池

每一个Class文件的常量池都不是固定的,所以有一个u2类型(也就是2个字节的)数据,来记录常量池的个数,但是这个个数与Java语言习惯不一样,它是从1开始计数的,也就是说00 21本来是有33个常量池,但是事实只有32个。关于这个问题的设计,可以追究到Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的是在于满足后面的某些指向常量池的索引值的数据在特定情况下表达“不引用任何一个常量池项目”的含义,这样情况及可以把索引值置为0 来表示。

当然了,常量池并不是我们想的那样,值放我们的public static final int _COUNT = 1这种常量值了,它主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References).这里的字面量就像我们Java语言层面的常量概念,就是文本常量,final型的常量值等等。而符号引用是属于编译原理方面的概念,包含了下面的常量:
1.类和接口的全限定名(Fully Qualified Name)
2.字段的名称和描述符(Descriptor)
3.方法的名称和描述符

当然了,这些都是什么意思,可能需要等我学习深入之后才能懂的吧。

到这里了,是不是应该来看这些常量池都是些什么东西吧。不急,想了解这个常量池的内容,还需要知道这张表的含义,Class文件就是通过查询这两张表,还获取常量池的内容。首先需要知道常量池中14项常量项的结构表:

看懂class文件 转看懂class文件 转

大家看到这边表可能不知道什么意思,那好,我们就用程序来说明一下,写个hello world程序:

1
2
3
4
5
6
7
8
9
10
public class T {
    //字符常量
    public static final int COUNT_NUMBER = 1 ;
    //main函数
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

编译的T文件用UltraEdit打开,如下图:

看懂class文件 转

紧靠在00 21后面的 就是我们的第一个常量池了,请看第一个红框,为什么是一个字节呢,因为常量池中最多14中不同的数据结构,一个字节(255)足够了,节约资源嘛。那么第一个红框中是0A,换成十进制就是10了,好了这次要查表了,什么表,就是上图那个表了啊,

看懂class文件 转

对,我们看到了是MethodRef_info,它的这种数据结构有三个属性组成:tag,index,index。tag值为10,标记是MethodRef属性的,还有两个index,index分别表示方法描述符的索引项和类型描述符的索引项,这里就不深究了,因为今天的内容只是为了读懂这个class文件,但是需要注意的是两个index都是u2类型的,那么就表明它们的值分别为00 06 和00 13,换成十进制就是6和19。

这个分析完了,这也就是我们第一个常量池中常量方法的分析了。 来看第二个:

看懂class文件 转

对,是09,我们查表:

看懂class文件 转

跟常量method属性一致,连个值分别为20,21,这里就不分析了,下一个:

看懂class文件 转

对,一下子到这里来,因为上面的都差不多,先查tag值,然后查询数据结构,看自己的Index所占的字节,然后获取数据,然后下一个。 这个是01,那就是字符串了:

看懂class文件 转

这次是length,看到结果是个00 0C,那么就是12了,那么就意味着后面的12个字节就是这个字符串的内容了,好了,看一下吧:

1
2
3
43 4F 55 4E 54 5F 4E 55 4D 42 45 52
转义为
COUNT_NUMBER

那么也就是我们自定义的常量字符了。 好了,到了这里我就不分析了,因为下去都一样了,其实Javap给我们提供了这种功能:

1
javap -verbose ClassName

看图:

看懂class文件 转

从这个图里,我们可以得到我们关于class的基本信息了,有次版本主版本,还有今天得常量池,看我们分析的也基本上和它一样了。

好了,今天就记录在这里吧,刚刚开始,感觉有些难,不知不觉写了这么多,其实理解起来不算很难吧。

也参考过老大的PPT,还有一些书籍,算了不写上去了,累啊。。。