字节码跨平台性与执行细节举例

概念简述

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

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

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

前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。

javac是一种能够将Java源码编译为字节码的前端编译器。

Javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析、语法解析、语义解析以及生成字节码。

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

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

Java的前端编译器

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编译器。(还可以自己设置为Aspect]编译器ajc)

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

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

问题

  1. 类文件结构有几个部分?
  2. 知道字节码吗?字节码都有哪些?Integer x=5; int y=5; 比较x == y都经过哪些步骤?

代码1

Integer有一个缓存,范围为-128~127,Integer i1 = 10,在字节码中实质是调用了Integer.valueOf,在此范围内返回的就是IntergerCache中的Integer对象,否则会返回新的Integer对象

public class IntegerTest {
    public static void main(String[] args) {
        /*
        	Integer x = 5;调用了调用Integer.valueOf 从数组中返一个Integer对象
			y 是 基本数据类型的5
			x == y的比较虽然表面看是引用类型和基本类型的比较,但是x会自动拆箱为基本数据类型,最终的比较就是x和y的值进行的比较,显然相等,true
         */
        Integer x = 5;
        int y = 5;
        System.out.println(x == y); //true

        // i1和i2在缓存范围内,使用的是内部缓存数组中的同一个对象,返回true
        Integer i1 = 10;
        Integer i2 = 10;
        System.out.println(i1 == i2); //true

        // i3和i4不在缓存范围内,他们是两个对象,false
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4); //false
    }
}

 0 iconst_5
 1 invokestatic #2 <java/lang/Integer.valueOf>
 4 astore_1
 5 iconst_5
 6 istore_2
 7 getstatic #3 <java/lang/System.out>
10 aload_1
11 invokevirtual #4 <java/lang/Integer.intValue>
14 iload_2
15 if_icmpne 22 (+7)
18 iconst_1
19 goto 23 (+4)
22 iconst_0
23 invokevirtual #5 <java/io/PrintStream.println>
26 bipush 10
28 invokestatic #2 <java/lang/Integer.valueOf>
31 astore_3
32 bipush 10
34 invokestatic #2 <java/lang/Integer.valueOf>
37 astore 4
39 getstatic #3 <java/lang/System.out>
42 aload_3
43 aload 4
45 if_acmpne 52 (+7)
48 iconst_1
49 goto 53 (+4)
52 iconst_0
53 invokevirtual #5 <java/io/PrintStream.println>
56 sipush 128
59 invokestatic #2 <java/lang/Integer.valueOf>
62 astore 5
64 sipush 128
67 invokestatic #2 <java/lang/Integer.valueOf>
70 astore 6
72 getstatic #3 <java/lang/System.out>
75 aload 5
77 aload 6
79 if_acmpne 86 (+7)
82 iconst_1
83 goto 87 (+4)
86 iconst_0
87 invokevirtual #5 <java/io/PrintStream.println>
90 return

代码2

str是通过StringBuilder通过append拼接之后toString返回的一个字符串,而StringBuilder的toString实质上就是new了一个String

str1是通过字面量方式加载到内存,保存到局部变量表中

所以str不等于 str1

public class StringTest {
    public static void main(String[] args) {
        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

new Son()先执行父类Father的构造函数

Father的构造函数调用了print(),由于子类重写了,所以打印的是子类son的x,从字节码看,这个时候x为0

然后Son调用自己的构造器,此时x为30

最后Father的x被赋值为20,f.x = 20

  • 实测如果 Son f = new Son(); 那么f.x = 40
/**
 * 成员变量的赋值过程:1 默认初始化 2 显式初始化 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;

    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.x=10
        // 20
        // Father f = new Father();
        // System.out.println(f.x);

        // son.x=0
        // son.x=30
        // 20
        Father f = new Son();
        System.out.println(f.x);
    }
}

Father

Son

posted @ 2021-03-05 19:47  我係死肥宅  阅读(144)  评论(0编辑  收藏  举报