大家好! 本文的翻译是专门为Java Developer课程的学生准备的。
我们将继续讨论Java虚拟机如何在内部工作。 在
上一篇文章 (
英文原版 )中,我们研究了类加载子系统。 在本文中,我们将讨论类文件的结构。
众所周知,所有用Java编程语言编写的源代码都首先使用Java开发工具包中的
javac
编译器编译为
字节码 。 字节码存储在特殊类文件中的二进制文件中。 然后,这些类文件由类加载器(ClassLoader)动态(如有必要)加载到内存中。
图-Java源代码编译每个具有
.java
文件都
.java
编译成至少一个
.class
文件。 对于源代码中定义的每个类,接口和模块,都会创建一个
.class
文件。 这也适用于接口和嵌套类。
注意-为简单起见,扩展名为
.class
文件将称为“类文件”。
让我们编写一个简单的程序。
public class ClassOne{ public static void main(String[] args){ System.out.println("Hello world"); } static class StaticNestedClass{ } } class ClassTwo{ } interface InterfaceOne{ }
为此文件运行
javac
将产生以下文件。
ClassOne$StaticNestedClass.class ClassOne.class ClassTwo.class InterfaceOne.class
如您所见,将为每个类和接口创建一个单独的类文件。
类文件中包含什么?
该类文件为二进制格式。 其中的信息通常在连续的信息之间没有缩进的情况下写入,所有内容都与字节边界对齐。 使用两个或四个连续的8位字节写入所有16位和32位值。
该类文件包含以下信息。
幻数,签名 。 每个类文件的前四个字节始终为
0xCAFEBABE
。 这四个字节标识Java类文件。
文件版本。 接下来的四个字节包含文件的主要版本和次要版本。 这些数字一起确定了类文件格式的版本。 如果类文件具有M的主要主要版本和次要的m,则我们将该版本指定为Mm
每个JVM在支持的类文件版本上都有限制。 例如,Java 11支持45至55的主要版本,Java 12支持45至56的主要版本。
常量池。 结构表,表示ClassFile结构及其子结构中的字符串常量,类名,接口,字段,方法和其他常量。 每个常量池元素都以一个定义常量类型的单字节标签开头。 根据常量的类型,以下字节可以是立即常量值,也可以是对池中另一个元素的引用。
访问标志。 指示类是公共接口还是私有接口的标志列表,是否为最终类。 Java虚拟机规范中描述了各种标志,例如
ACC_INTERFACE
,
ACC_ENUM
,
ACC_INTERFACE
,
ACC_ENUM
等。
这个课 链接到常量池中的条目。
超级班。 链接到常量池中的条目。
介面 类实现的接口数。
字段数。 类或接口中的字段数。
领域。 在字段数之后,是一个可变长度的结构表。 每个字段一个,带有字段类型和名称的描述(参考常量池)。
方法数量。 类或接口中方法的数量。 此数字仅包括在类中明确定义的方法,而没有从超类继承的方法。
方法 接下来是方法本身。 对于每个方法,包含以下信息:方法描述符(返回类型和参数列表),方法的局部变量所需的字数,方法操作数堆栈所需的最大堆栈字数,方法捕获的异常表,方法字节码和表行号。
属性数。 此类,接口或模块中的属性数。
属性 属性数量后跟描述每个属性的表或可变长度结构。 例如,始终有一个“ SourceFile”属性。 它包含从中编译类文件的源文件的名称。
尽管类文件不是直接可读的,但JDK中有一个名为
javap的工具,它以方便的格式显示其内容。
让我们编写一个简单的Java程序,如下所示。
package bytecode; import java.io.Serializable; public class HelloWorld implements Serializable, Cloneable { public static void main(String[] args) { System.out.println("Hello World"); } }
让我们使用
javac
编译该程序,该程序将创建
HelloWorld.class
文件,并使用
javap
查看
HelloWorld.class
文件。 对
HelloWorld.class
使用
-v (verbose)
选项运行
javap
会得到以下结果:
Classfile /Users/apersiankite/Documents/code_practice/java_practice/target/classes/bytecode/HelloWorld.class Last modified 02-Jul-2019; size 606 bytes MD5 checksum 6442d93b955c2e249619a1bade6d5b98 Compiled from "HelloWorld.java" public class bytecode.HelloWorld implements java.io.Serializable,java.lang.Cloneable minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // bytecode/HelloWorld super_class: #6 // java/lang/Object interfaces: 2, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #6.#22
在这里,您可以看到该类是公共的,并且在常量池中有37个条目。 有一个属性(下面的SourceFile),该类实现了两个接口(Serializable,Cloneable),没有字段,并且有两个方法。
您可能已经注意到,源代码中只有一个静态main方法,但是类文件却说有两种方法。 记住默认构造函数-这是
javac
编译器添加的无参数构造函数,其字节码在输出中也可见。 构造函数被视为方法。
您可以
在此处阅读有关javap的更多信息。
提示 :您还可以使用javap查看lambda与匿名内部类的区别。