字节码文件的跨平台性
Java 语言:跨平台的语言(write once, run anywhere)
- 当 Java 源代码成功编译成字节码后,如果想在不同的平台上面运行, 则无须再次编译
- 但是 这个优势不再那么吸引人了。Python、PHP、Perl、Ruby、Lisp 等有强大的解释器
- 跨平台似乎已经快称为一门语言必选的特性
Java 虚拟机:跨语言的平台
- Java 虚拟机不和包括 Java 在内的任何语言绑定,它只与”Class 文件”这种特定的二进制文件格式所关联。无论使用何种语言进行软件开发, 只要能将源文件编译为正确的 Class 文件,那么这种语言就可以在 Java 虚拟机上执行,可以说,统一而强大的 Class 文件结构,就是 Java 虚拟机的基石、桥梁。
- https://docs.oracle.com/javase/specs/index.html
中篇以java8为主体进行讲解https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
3.想要让一个Java程序正确地运行在JVM中,Java源码就必须要编译为符合JVM规范的字节码。
- 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。
- javac是一种能够将Java源码编译为字节码的前端编译器
- javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤:分别是词法解析,语法解析,语义解析,生成字节码
- Oracle的JDK软件包括两部分内容:
- 一部分是将Java源代码编译成Java虚拟机的指令集的编译器(前端编译器不包含在jvm中)
- 另一部分是用于实现Java虚拟机的运行时环境
java的前端编译器
前端编译器 vs 后端编译器
- Java源代码的编译结果是字节码,需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的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 Compiler(Ahead Of Time Compiler),静态提前编译器,程序运行之前将字节码文件直接翻译成机器指令
通过字节码指令看代码细节
BAT 面试题
- 类文件结构有几个部分?
- 知道字节码吗?字节码都有哪些?Integer x = 5; int y = 5;比较 x == y 都经过哪些步骤?
代码举例1
public class IntegerTest {
public static void main(String[] args) {
/*
* 通过字节码和源码可以看出,当Integer的范围在 -128 ~ 127 之间会在数组中直接拿取数值
* 超过这个范围会重新 new 对象
* */
Integer x = 5;
int y = 5;
System.out.println(x == y); // true,拆箱,比较数值
Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1 == i2); // true
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false
}
}
代码举例2:
package com.atguigu.java;
public class StringTest {
public static void main(String[] args) {
//通过字节码发现new了StringBuilder,append后再toString返回str
//toString方法重新new了一个String对象
String str = new String("hello") + new String("world");
String str1 = "helloworld";
System.out.println(str == str1); //false
String str2 = new String("helloworld");
System.out.println(str == str2); //false
}
}
代码举例3:
/*
成员变量(非静态的)的赋值过程: ① 默认初始化 - ② 显式初始化 /代码块中初始化 - ③ 构造器中初始化 - ④ 有了对象之后,可以“对象.属性”或"对象.方法"
的方式对成员变量进行赋值。
*/
class Father {
int x = 10;
public Father() {
this.print();
x = 20;
}
public void print() {
System.out.println("Father.x = " + x);
}
}
class Son extends Father {
int x = 30;
// float x = 30.1F;
public Son() {
this.print();
x = 40;
}
public void print() {
System.out.println("Son.x = " + x);
}
}
public class SonTest {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);
}
}
先看第一种情况比较简单:Father f = new Father();
运行结果:
再看情况2:Father f = new Son();
运行结果:
由于创建的是子类对象,print方法被重写,所以父类构造器的this.print调用的是子类的print
但是属性没有多态,所以f.x=20