ccs加载out文件_class文件结构1——魔数、版本号、常量池与类访问标记

1、魔数

我们可以利用editplus,以16进制的编码格式来查看class文件的结构,具体操作方法为在editplus的工具栏中点击Edit,下拉选择Hex Viewer即可。

debe3bb7a65cda8d6f47f2bdfca6ab5e.png

如图所示,前四(4个bit位*8个字母=32,32/8=4字节)个字节为0xCAFEBABE,这就是class文件的魔数。

虚拟机借助魔数,用来识别.class 文件,虚拟机在加载类文件之前会先检查魔数,如果不是 0xCAFEBABE 则拒绝加载该文件。

关于class文件魔数的由来,可以参考这篇文章class文件魔数CAFEBABE的由来


2、版本号

版本号分为副版本号(minor version)与主版本号(major version),紧随魔数之后。

c602c07cdae2eccd133868a0f72ed85d.png

可以看到主版本号为52(3*16+4),52对应的java版本为java8,规律就是java版本=主版本号-44。例如主版本号50对应的java版本为java6。

如果java6的虚拟机去加载一个java8编译的类,则虚拟机直接会抛出java.lang.UnsupportedClassVersionError。

我们使用javap -v,也可以直接看到class文件的主副版本号:

5cc5e3000f3919b1a73be54a58cc3205.png

3、常量池

常量池紧随着版本号,是class文件中最为复杂的部分。

当执行一个java方法时,需要将操作数入栈,这个时候如果操作数很小,那么直接内嵌到字节码中。如果是比较大的数字或者是字符串时,就不再会内嵌到字节码中,而是存到常量池中。当将这些操作数入栈时,字节指令后面会跟着一个指向常量池的一个索引。

比如这个方法:

    public void print() {        System.out.println(1);        System.out.println("abcd");    }

对应字节码为:

90c2e45d93fdf01de4d2e9669cc51bf7.png

当然,常量池不仅仅存储字符串类型,完整的常量类型,如下表所示:

546c1c12bdc52169782e1b7beafcf373.png

符号引用可以这么去理解:符号引用是一个具有定位意义的字符串,在类加载过程中,连接的子过程解析阶段时,虚拟机会将符号引用解析为直接引用。关于类加载机制,可以先参考我的另外一篇文章类的奇幻漂流——类加载机制探秘

就以我们最经常用到的System.out.println()方法为例,out是System类中的一个PrintStream类型的引用变量,println则是PrintStream类中的一个方法,那么out字段的符号引用与println方法的符号引用是什么样子的呢?

以下面的代码为例:

package com.yang.testMethod;public class Main {    public static void main(String[] args) {        System.out.println(1);    }}

常量池如下:

c36d8ddb80e21ec83bef882d5dfd7774.png

可以看得出来,out字段的Fieldref=Class+NameAndType,即字段的符号引用=所属类的符号引用+字段的描述符。

println方法的符号引用也是同样的组成方式,但方法的NameAndType包含参数类型描述符以及返回值类型描述符。

描述符又是怎样组织的,可以先看字段表中的字段描述符以及方法表中的方法描述符。

MethodHandle、MethodType与InvokeDynamic是为了支持动态语言调用,在1.7之后才加入的,这里不做讨论。不过这里的invokeDynamic很有意思,会另外篇幅进行介绍。


4、类访问标记

类访问标记紧随在常量池之后,占两个字节,一共16位,目前只使用了其中8位。

虚拟机在编译某个类时,会解析出这个类的特性,将其设置到类访问标记上,即将特定的bit位置1,表示该类拥有这个bit位上代表的标记。

8种标记如下表所示,例如编译一个public类时,该类的访问标记上会有ACC_PUBLIC与ACC_SUPER。

1d917d416160566baa89f0849766fa3c.png

例如,有这样的一个java文件:

package com.yang.testFlag;public interface Main {}

使用javac Main.java,接着javap -v Main之后,得到该接口的访问标记为:

71d3b2b9b194f2c8400e719fd7d471d3.png

接口是一种特殊的抽象类,所有的变量都为public static final类型,所有的方法都是抽象方法。(当然除了静态方法与默认方法)。更多关于抽象类与接口的特征与区别,可以先参考我的另外一篇文章抽象类和接口的联系与区别。

因此,一个public类型的接口,它的访问标记有3个,分别为ACC_PUBLIC、ACC_INTERFACE与ACC_ABSTRACT。


版权声明:本文为weixin_27038245原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。