要在JVM中执行java代码必须要编译为class文件,JDK是如何将Java代码编译为class文件,这种机制通常被称为Java源码编译机制。
1、JVM定义了class文件的格式,但是并没有定义如何将java源码编译为class文件,各个厂商在实现JDK时候通常会将符合java语言规范的源码编译为class文件的编译器,如JDK就是javac
javac编译生成class文件的步骤如下:
1、分析和输入到符号表 (Parse and Enter )
Parse过程所做的为词法和语法分析,词法分析(com.sun.tools.javac.parser.Scanner)要完成的是将代码字符串转变为token序列(例如Token.EQ(name:=));语法分析(com.sun.tools.javac.parser.Parser)要完成的是根据语法由token序列生成抽象语法树。
Enter(com.sun,tools,javac.comp.Enter)过程为将符号输入到符号表,通常包括确定类的超类型和接口,根据需要添加默认构造器,将类中出现的符号输入类自身的符号表中等。
2、注解处理(Annotation Processing)
该步骤主要用于处理用户自定的Annotation,可能带来的好处是基于annotation来生成附加的代码或进行一些特殊的检查,从而节省一些共用的代码的编写,例如当采用Lombok时,可大大减少代码量,编译是引入Lombok对实体进行编译后,再通过javap查看class文件,发现自动成get set toString等方法,此功能基于JSR269,在JDK1.6中提供支持,在Annotation Processing进行后,再次进入Parse and Enter步骤
3、语义分析和生成class文件(Analyse and Generate)
Analyse步骤基于抽象语法树进行一系列的语法分析,包括将语法树中的名字,表达式等元素与变量,方法,类型等联系在一起,检查变量使用前是否声明,推导泛型方法的类型参数,检查类型匹配性,进行常量折叠;检查所有语句都可到达,检查所有checked exception都被捕捉或者抛出,检查变量的确定性赋值(如有返回值的方法必须定有返回值);检查变量的确定性不重复赋值(例如声明为final的变量);解除语法糖(消除if(false) )形式的无用代码;将泛型Java转为普通的Java,将含有语法糖的语法树改为含有简单语言结构的语法树,例如for each循环,自动装箱,拆箱等
在完成语义解析后,开始生成class文件(com.sun.tools.javac.jvm.Gen)生成的步骤,首先将实例成员初始化器手机到构造器中,将静态成员初始化器手机为<cinit>();接着将抽象语法树生成字节码,采用的方法为后序遍历语法树,并进行最后的少量代码转换(例如String相加转变为StringBuilder操作);最后从符号表生成class文件。
生成class文件除了javac之外,还可以通过ECJ(Eclipse compiler for java) 或者是Jikes等编译器来将Java源码编译为class文件。
class文件不仅仅存放了字节码,还存放了很多辅助JVM来执行class的附加信息,一个class文件包括
-结构信息
包括class文件格式版本号及各部分的数量和大小信息
-元数据
简单的说可以认为是方法信息对用的java源码中语句,表达式对应的信息,主要有字节码,异常处理表,求值栈与局部变量区大小,求值栈的类型记录,调试用符号信息。
可以通过javac -g HelloWord.java 编译生成class文件,然后通过javap -c -s -l -verbose HelloWord来查看编译后的class文件。
class文件是完整的自描述文件,字节码在其中只占了很小的部分,源码编译为class文件后,即可放入到jvm中执行,执行时jvm首先要做的是装载class文件,这个机制通常称为类加载机制。