1、魔数

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

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

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

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


2、版本号

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

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

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

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


3、常量池

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

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

比如这个方法:

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

对应字节码为:

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

类型

说明

CONSTANT_Utf8_info

表示utf-8编码的字符串常量

CONSTANT_Integer_info

表示int常量

CONSTANT_Float_info

表示float常量

CONSTANT_Long_info

表示long常量

CONSTANT_Double_info

表示double常量

CONSTANT_Class_info

表示类或接口的完全限定名

CONSTANT_String_info

表示java.lang.String类型的字符串

CONSTANT_Fieldref_info

表示字段的符号引用

CONSTANT_Methodref_info

表示方法的符号引用

CONSTANT_InterfaceMethodref_info

表示实现的接口方法的符号引用

CONSTANT_NameAndType_info

表示字段或方法的名称以及类型

CONSTANT_MethodHandle_info

表示方法句柄

CONSTANT_MethodType_info

表示方法类型

CONSTANT_InvokeDynamic_info

表示动态调用

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

就以我们最经常用到的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);
    }
}

常量池如下:

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

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

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

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


4、类访问标记

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

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

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

标记名称

说明

ACC_PUBLIC

类或接口的访问权限为public

ACC_FINAL

类被final修饰

ACC_SUPER

ACC_INTERFACE

接口

ACC_ABSTRACT

抽象类或接口

ACC_SYNTHETIC

编译器自动生成,不是用户对代码编译生成

ACC_ANNOTATION

注解

ACC_ENUM

枚举

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

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

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

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

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