1 字节码文件的跨平台性

  1. Java语言:跨平台的语言

√ 当Java源代码成功编译成字节码后,如果想在不同的平台上面运行,则无须再次编译
√ 这个优势不再那么吸引人了。Python、PHP、Per1、Ruby、Lisp等有强大的解释器。
√ 跨平台似乎已经快成为一门语言必选的特性。

  1. Java虚拟机:跨语言的平台

Java虚拟机不和包括Java 在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。无论使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行。可以说,统一而强大的class文件结构,就是Java虚拟机的基石、桥梁。
image.png
image.png
https://docs.oracle.com/javase/specs/index.html

  • 所有的JVM全部遵守Java虚拟机规范,也就是说所有的JVM环境都是一样的,这样一来字节码文件可以在各种JVM上运行

3.想要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码。

  • 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。
  • javac是一种能够将Java源码编译为字节码的前端编译器。
  • Javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析、语法解析、语义解析以及生成字节码。

image.png
Oracle的JDK软件包括两部分内容:

  • 一部分是将Java源代码编译成Java虚拟机的指令集的编译器。
  • 另一部分是用于实现Java虚拟机的运行时环境。

    2 Java的前端编译器

    image.png
    前端编译器 VS 后端编译器
    Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器。
    HotSpot VM并没有强制要求前端编译器只能使用javac来编译字节码,其实只要编译结果符合JVM规范都可以被JVM所识别即可。在Java的前端编译器领域,除了javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的ECJ (Eclipse Compiler for Java)编译器。和Javac的全量式编译不同,ECJ是一种增量式编译器。

  • 在Eclipse中,当开发人员编写完代码后,使用“Ctrl+S”快捷键时,ECJ编译器所采取的编译方案是把未编译部分的源码逐行进行编译,而非每次都全量编译。因此ECJ的编译效率会比javac更加迅速和高效,当然编译质量和javac相比大致还是一样的。

  • ECJ不仅是Eclipse的默认内置前端编译器,在Tomcat中同样也是使用ECJ编译器来编译jsp文件。由于ECJ编译器是采用GPLv2的开源协议进行源代码公开,所以,大家可以登录eclipse官网下载ECJ编译器的源码进行二次开发。
  • 默认情况下,IntelliJ IDEA使用javac编译器。(还可以自己设置为AspectJ编译器 ajc)

前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。

复习:AOT(静态提前编译器,Ahead Of Time Compiler)
例如:calss HelloWorld{}
编译:javac HelloWorld.java

3 透过字节码指令看代码细节

  1. BAT面试题
    √ 类文件结构有几个部分?
    √ 知道字节码吗?字节码都有哪些? Integer x = 5;int y = 5;比较x == y 都经过哪些步骤?
    2.代码举例
    代码举例1:
    image.png
    如果Integer的值是在-128到127之间,会直接返回Integer对象,他会在Integer初始化的Integer数组中取出存放的值,数组的长度就是-128到127范围,如果不是这个范围,他就会重新new一个Integer对象,所以i3和i4是两个对象,所以不相等,而i1和i2是同一个对象
    image.png
    image.png
    代码举例2:
    image.png
    答案是false,newString()进行拼接的时候会调用StringBuilder,拼接完后StringBuilder会掉toString方法,toString方法内部是new了一个String对象,所以应用str是一个new String()的一个对象,而Str1是String的字面量,所以不会相等

image.png
代码举例3:

  1. class Father{
  2. int x = 10;
  3. public Father(){
  4. this.print();
  5. x = 20;
  6. }
  7. public void print(){System.out.println("Father.x =" + x);}
  8. }
  9. class Son extends Father{
  10. int x = 30;
  11. public Son(){
  12. this.print();
  13. x = 40;
  14. }
  15. public void print(){System.out.println("Son.x = " + x);}
  16. }
  17. public class SonTest{
  18. public static void main(String[] args){
  19. Father f = new Son();
  20. System.out.println(f.x);
  21. }
  22. }
  23. //运行结果:
  24. //Son.x = 0
  25. //Son.x = 30
  26. //20

成员变量(非静态的)的赋值过程:①默认初始化 - ②显示初始化/代码块中的初始化 - ③构造器中初始化 - ④ 有了对象之后,可以“对象.属性”或“对象.方法”的方式对成员变量进行赋值。
当执行Father f = new Son()的时候,因为Son类继承了Father类,所以先初始化Father类,在执行Father构造函数的时候,构造函数调了pirnt()方法,因为Son重写了Father的print()方法,所以Father中的构造函数调print()方法的时候是调的Son重写过后的print()方法,这个时候Son中的x还没被赋值,所以Son.x 是等于0,Father初始化完成后再执行Son的初始化,这个时候Son的构造函数调了print()方法,这个时候的x显示赋值已执行,所以Son.x = 30。因为属性不存在多态性,所以 f.x 就是调的父类的 x 属性,所以 f.x = 20。
image.png
image.png
image.png