前言
Java运行在Java虚拟机之上,因此Java语言是跨平台的一门面向对象语言。一处编译,处处运行。是否了解JVM对码出高效代码起到非常关键的作用。对任何一个Java开发者而言。只要是走在大牛的路上,JVM不得不学。
Java代码如何编译
如果要在JVM中执行Java代码。首先就需要由Java文件产生class文件,我们把这个过程称为“编译”。这种机制就称为Java源码的编译机制。通过java -version查看我们开发中使用的JDK版本。如下图所示,
1 java version "1.8.0_171" 2 Java(TM) SE Runtime Environment (build 1.8.0_171-b11) 3 Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
第3行:HotSpot(TM)。在HotSpot中Java源码的编译机制就是javac。在理论上来讲Java源码的编译机制总共分为三部分。
第一部分:分析和输入到符号表。parse 和 enter。
第二部分:注解处理。Annotation Processing
第三部分:语义分析和生成class文件。Analyse and Generate
①分析和输入到符号表
分析包括了语法分析和语义分析。即parse的过程。词法分析将代码字符串转变为token序列;词法分析要根据语法由token序列生成抽象语法树。输出到符号表,即enter的过程。包含了类的超类型和接口等类中出现的符号输入类自身的符号表中。
②注解的处理
编译器对Java源代码中开发者的自定义注解,用来参数的特殊检查和动态在编译阶段产生代码。这个理论非常重要,动态在编译阶段动态的产生代码,笔者自己早就有想法,构想通过这一项理论在系统级别源码阶段不需要开发者作过多的重复性的工作。想要的效果代码在编译阶段产生。
③语义分析和生成class文件
语义分析这一步主要是基于抽象树来进一步的一系列的语义分析,包含语法树中的名字,表达式等元素与变量,方法,类型联系在一起。同时检查一个变量在使用之前是否已经声明等等。在完成了语义分析之后,开始生成class文件。
class文件中除了包含字节码信息之外。还包括了结构信息(class文件格式的版本号,以及各个部分的数量与大小信息)、元数据(源代码中的声明和变量)、方法信息(语句和表达式)。
Class文件如何被JVM识别
这就是类加载过程。所有的程序都需要加载到内存之后才能与计算机交互。通过编译器编译好的class文件同样需要加载到内存中。这里的内存指的是Java虚拟机内存中。
这就需要用到加载器,什么加载器?顾名思义就是负责加载class文件,读取数据到内存中的loader。我们把这个loader叫作ClassLoader。类加载器。Java的类加载器是运行时的核心。主要的功能是在启动时,对类,加载,链接,初始化。
①加载。读取class文件产生二进制流。并且转化为特定的数据结构。简单校验这个类是否是合法。常量池。文件长度,是否有父类。然后创建对应的类的java.lang.Class实例。
②链接。链接阶段分为三个步骤。包含验证,准备,解析。验证则是更加详细的校验,final是不是合规,类型是不是正确。静态变量是不是合理。准备阶段是为静态变量分配内存,并且设定默认值。解析是为类和方法等定位直接引用。完成内存结构布局。
③初始化。初始化阶段是执行类构造器的<clinit>方法。
类加载时讲一个class文件实例化成Class对象。并进行相关初始化的过程。
如何查看字节码指令
java源文件被编译器编译为class文件,这个class文件的存储包含了字节码。那么如何来查看这个字节码信息呢?如下代码所示:
1 package com.example.jvm; 2 3 public class Simple { 4 5 6 public int simpleMethod() { 7 int x = 5; 8 int y = 7; 9 10 int z = x+y; 11 12 int a = ++x; 13 int b = y++; 14 15 System.out.println("a="+a); 16 System.out.println("b="+b); 17 System.out.println("z="+z); 18 19 return z; 20 } 21 22 23 public static void main(String[] args) { 24 Simple simple = new Simple(); 25 simple.simpleMethod(); 26 } 27 }
我们执行编译的指令,在 HotSpot中就是javac Simple.java。此时在同级目录下新增一个class文件。我们继续执行javap -c Simple。得到的结果如下图所示:
1 public class com.example.jvm.Simple { 2 public com.example.jvm.Simple(); 3 Code: 4 0: aload_0 5 1: invokespecial #1 // Method java/lang/Object."<init>":()V 6 4: return 7 8 public int simpleMethod(); 9 Code: 10 0: iconst_5 11 1: istore_1 12 2: bipush 7 13 4: istore_2 14 5: iload_1 15 6: iload_2 16 7: iadd 17 8: istore_3 18 9: iinc 1, 1 19 12: iload_1 20 13: istore 4 21 15: iload_2 22 16: iinc 2, 1 23 19: istore 5 24 21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 25 24: new #3 // class java/lang/StringBuilder 26 27: dup 27 28: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 28 31: ldc #5 // String a= 29 33: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 30 36: iload 4 31 38: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 32 41: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 33 44: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 34 47: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 35 50: new #3 // class java/lang/StringBuilder 36 53: dup 37 54: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 38 57: ldc #10 // String b= 39 59: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 40 62: iload 5 41 64: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 42 67: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 43 70: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 44 73: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 45 76: new #3 // class java/lang/StringBuilder 46 79: dup 47 80: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 48 83: ldc #11 // String z= 49 85: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 50 88: iload_3 51 89: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 52 92: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 53 95: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 54 98: iload_3 55 99: ireturn 56 57 public static void main(java.lang.String[]); 58 Code: 59 0: new #12 // class com/example/jvm/Simple 60 3: dup 61 4: invokespecial #13 // Method "<init>":()V 62 7: astore_1 63 8: aload_1 64 9: invokevirtual #14 // Method simpleMethod:()I 65 12: pop 66 13: return 67 }
第10行:iconst_5代表将常量5压入到操作栈。
第11行:istore_1代表将常量5保存到局部变量表的slot_1中。
第12行:bipush 7代表将常量7压入到操作栈。
第13行:istore_2代表将常量7保存到局部变量表的slot_2中。
第14行:iload_1代表将局部变量表中的slot_1元素(int x)压入操作栈。
第15行:iload_2代表将局部变量表中的slot_2元素(int y)压入操作栈。
第16行:IADD代表将两个数都取出来,计算出来之后,压回到操作栈中的栈顶。
任何一行代码,在被编译器编译时,都会对应一条或者多条指令。上面只是简单说明,后面会集中式对这些指令的讲解。