前言:
java程序运行在JVM之上,JVM的运行状况对于java程序会产生很大的影响,所以掌握JVM中关键的机制对与编写稳定的,高性能的java程序至关重要!
JVM标准结构图
JVM负责装载class文件并执行,首先要掌握以下三个问题
1.JDK是如何将java代码编译为class文件的?
2.如何装载class文件?
3.如何执行class?
将源码编译class文件的实现取决于各个JVM实现或 各种源码编译器
class文件通常由类加载器classLoader来完成加载
class的执行在SUN JDK中有解释执行和编译为机器码执行两种方式。
本文主要详细介绍java源码编译机制
java源码编译机制
JVM规范中定义了class文件的格式,但没有规定java源码如何编译为class文件,各厂商都会实现将符合java语言规范的源码编译为class文件的编译器,
如Sun JdK中就是javac,javac将源码编译为class文件的步骤如图所示
下面简单介绍三个步骤
1.分析和输入到符号表(Parse and Enter)
Parse所做的为词法和语法分析
词法分析:
将代码字符串转变为
token序列
token就是把程序的语句进行类似分词得到的单词
语法分析:
根据语法由tiken序列生成
抽象语法树
Enter
做的是将符号输入到符号表
通常包括确定类的超类型和接口,根据需要添加默认构造器,将类中出现的符号输入类自身的符号表中去
2.注解处理(Annotation Processing)
该步骤主要用于处理用户自定义的注解 ,如以下代码
import lombok.Getter;
public class User{
private @Getter String name;
public static void main(String args[]){
String mango="mango";
String s="abc"+mango+"def";
System.out.println(s);
}
}
编译时引入Lombok对User.java进行编译后,可以通过javap查看class文件自动生成了public String getUsername()方法
javac -cp lombok-1.16.16.jar User.java
javap User
关于Lombok 看考http://blog.csdn.net/ghsau/article/details/52334762
在Annotation Processing进行后,再次进入了Pare and Enter步骤
3.语义分析和生成class文件(Analyse and Generate)
Analyse语义分析:基于抽象语法树进行一系列的语义分析
1.包括将树中的名字,表达式等变量,方法,类型联系到一起
2.检查变量使用前是否已声明
3.检查checked exception都被捕获或抛出
4.将泛型java转为普通java
5.将含有语法糖的语法树改为含有简单语法的语言结构的的语法树,如for exch,自动装箱拆箱等
.......
完成了语义分析后,就开始生成class文件。
生成的步骤为
1.将实例成员初始化器收集到构造器中
2.将静态成员初始化器收集为《clinit》
3.将抽象语法树生成字节码,采用的方法为
后续遍历 语法树
4.进行最后的少量代码转换(例如string相加转变为stringBuilder相加)
5.从符号表生成class文件
除了javac外,还可以通过Eclipse Complier for java 或jikes等编译器来讲java文件编译为class文件
class文件结构
class文件结构不仅仅存放了字节码,还包含了很多辅助jvm来执行的clasas附加信息,一个class文件包含了以下信息
结构信息
包含class文件格式版本后及各部分的数量与大小信息
元数据
java源码中声明与常量的信息,如类、超类,实现的接口声明的信息,域field与方法声明信息和常量池
方法信息
java源码语句与表达式对应的信息
以一段简单的代码来说明class文件结构
public class Foo {
private static final int MAX_COUNT = 1000;
private static int count = 0;
public int bar() throws Exception{
if(++count<MAX_COUNT){
count = 0;
throw new Exception("count overflow");
}
return count;
}
}
javac -g Foo.java
编译后,使用
javap -c -s -l -verbose Foo 查看编译后的class文件
E:\test>javap -c -s -l -verbose Foo
Classfile /E:/test/Foo.class
Last modified 2017-10-22; size 607 bytes
MD5 checksum f7e8e8b3a4f1f3a7acefb5253d8f43df
Compiled from "Foo.java"
//元数据:类/继承的超类/实现的接口的声明信息
public class Foo
minor version: 0
//50表示jdk 6 ,51表示jdk 7, 52表示jdl 8
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//元数据:常量池
Constant pool:
#1 = Methodref #7.#27 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#28 // Foo.count:I
#3 = Class #29 // Foo
#4 = Class #30 // java/lang/Exception
#5 = String #31 // count overflow
#6 = Methodref #4.#32 // java/lang/Exception."<init>":(Ljava/lang/String;)V
#7 = Class #33 // java/lang/Object
#8 = Utf8 MAX_COUNT
#9 = Utf8 I
#10 = Utf8 ConstantValue
#11 = Integer 1000
#12 = Utf8 count
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 LFoo;
#20 = Utf8 bar
#21 = Utf8 ()I
#22 = Utf8 StackMapTable
#23 = Utf8 Exceptions
#24 = Utf8 <clinit>
#25 = Utf8 SourceFile
#26 = Utf8 Foo.java
#27 = NameAndType #13:#14 // "<init>":()V
#28 = NameAndType #12:#9 // count:I
#29 = Utf8 Foo
#30 = Utf8 java/lang/Exception
#31 = Utf8 count overflow
#32 = NameAndType #13:#34 // "<init>":(Ljava/lang/String;)V
#33 = Utf8 java/lang/Object
#34 = Utf8 (Ljava/lang/String;)V
{
//Enter:将符号输入到符号表是生成的默认构造器方法
public Foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LFoo;
//bar方法的元数据信息
public int bar() throws java.lang.Exception;
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
//方法对应的字节码信息
0: getstatic #2 // Field count:I
3: iconst_1
4: iadd
5: dup
6: putstatic #2 // Field count:I
9: sipush 1000
12: if_icmpge 29
15: iconst_0
16: putstatic #2 // Field count:I
19: new #4 // class java/lang/Exception
22: dup
23: ldc #5 // String count overflow
25: invokespecial #6 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
28: athrow
29: getstatic #2 // Field count:I
32: ireturn
//行号信息
LineNumberTable:
line 5: 0
line 6: 15
line 7: 19
line 9: 29
//局部变量信息
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 this LFoo;
StackMapTable: number_of_entries = 1
frame_type = 29 /* same */
//异常处理器表
Exceptions:
throws java.lang.Exception
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #2 // Field count:I
4: return
LineNumberTable:
line 3: 0
}
SourceFile: "Foo.java"
从上可见,class文件是个完整的自描述文件,字节码在其中只占了很小的一部分。
参考:分布式java应用 基础与实践