Java面试题(2)
1、什么是JAVA的字节码(.class)文件
在 Java 中,字节码(.class)文件是 Java 编译器(javac)将 Java 源文件(.java)编译后生成的一种中间格式文件。它包含了 Java 虚拟机(JVM)能够理解的指令和符号表等信息。字节码文件是平台无关的,这是 Java“一次编写,到处运行” 特性的关键所在。
字节码构成:
- 魔数(Magic Number):字节码文件的开头 4 个字节是魔数,固定值为 0xCAFEBABE。这个魔数用于识别该文件是否为一个有效的 Java 字节码文件,就像文件的 “身份证” 一样,JVM 在加载类文件时首先会检查这个魔数。
- 版本信息:紧跟魔数之后的是版本信息,包括主版本号和次版本号。这用于 JVM 判断该字节码文件是否能被当前 JVM 版本所支持。例如,如果字节码文件的版本过高,而 JVM 版本较低,JVM 可能无法正确加载和执行该字节码文件。
- 常量池(Constant Pool):常量池是字节码文件中非常重要的一个部分,它包含了各种字面量(如字符串常量、整数常量等)和符号引用(如类和接口的全限定名、字段和方法的名称和描述符等)。常量池就像是一个资源仓库,字节码指令在需要这些信息时会从中获取。例如,当执行一个字符串拼接操作时,用到的字符串常量就从常量池中获取。
- 访问标志(Access Flags):用于表示类或者接口的访问权限等信息,如该类是 public、final 还是 abstract 等。这些标志位可以帮助 JVM 在加载类时确定类的访问性质。
- 类索引、父类索引和接口索引集合:这些部分用于确定类的继承关系。类索引指向该类的全限定名在常量池中的索引,父类索引指向父类的全限定名在常量池中的索引,接口索引集合则包含了该类实现的接口的全限定名在常量池中的索引。通过这些信息,JVM 可以构建出类的继承层次结构。
- 字段表集合(Fields):描述了类或者接口中声明的变量。对于每个字段,会记录其访问修饰符、名称、描述符等信息。例如,一个简单的 Java 类中有一个私有整型变量,字节码文件的字段表集合中就会记录这个变量是 private 的,它的名称以及它是一个整型变量等信息。
- 方法表集合(Methods):包含了类或者接口中声明的方法的信息。对于每个方法,会记录其访问修饰符、名称、描述符、方法体中的字节码指令等。例如,一个有参数的方法,字节码文件会记录方法的参数类型和返回类型等信息,以及方法体中的实际操作指令,这些指令用于在 JVM 执行方法时进行运算、控制流程等操作。
- 属性表集合(Attributes):可以包含多种不同的属性信息,比如代码属性(用于存储方法体的字节码指令)、源文件属性(记录源文件的名称)、行号表属性(用于在调试时将字节码指令和源文件中的行号对应起来)等。这些属性为字节码文件提供了额外的信息,以满足不同的需求,如调试和安全检查等。
我们需要格外注意的是 .class->机器码
这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT(Just in Time Compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
2.AOT和JIT的区别
- AOT(Ahead - Of - Time)编译:是指在程序运行之前,将字节码文件直接编译成机器码的过程。这种编译方式可以在程序部署之前完成所有的编译工作,生成的机器码可以直接在目标机器上运行。
- JIT(Just - In - Time)编译:是在程序运行过程中,将字节码动态地编译成机器码的编译方式。JIT 编译器会根据程序的运行情况,在需要的时候对字节码进行编译,并且会对频繁执行的代码段进行优化编译。
- AOT 编译
- AOT 编译生成的机器码通常会占用更多的磁盘空间,因为所有可能执行的代码都被提前编译成了机器码。同时,在内存方面,由于机器码的体积可能较大,加载到内存中时也会占用较多的内存空间。
- JIT 编译
- JIT 编译在内存占用方面相对灵活。它只会在需要的时候编译字节码,而且可以对编译后的机器码进行缓存和管理。在代码大小方面,字节码文件本身相对较小,并且只有被频繁调用的部分才会被编译成机器码,所以整体上内存占用和代码大小在初始阶段可能比较小。