ITPub博客

首页 > Linux操作系统 > Linux操作系统 > Java源码编译机制

Java源码编译机制

原创 Linux操作系统 作者:AllenHU0320 时间:2011-09-05 21:44:41 0 删除 编辑

《分布式Java应用:基础与实践》林昊 电子工业出版社 第3章深入理解JVM,本章以Sun JDK 1.6为例来对JVM进行介绍。本节为大家介绍Java源码编译机制。

3.1.1  Java源码编译机制

JVM规范中定义了class文件的格式,但并未定义Java源码如何编译为class文件,各厂商在实现JDK时通常会将符合Java语言规范的源码编译为class文件的编译器,例如在Sun JDK中就是javac,javac将Java源码编译为class文件的步骤如图3.2所示。

源码文件->分析和输入到符号表(Parse and Enter)->注解处理(Annotation Processing)->语义分析和生成class文件(Analyse and Generate)->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 时,可编写如下代码:

  1. public class User{  
  2.     private @Getter String username;  

编译时引入Lombok对User.java进行编译后,再通过javap查看class文件可看到自动生成了public String getUsername()方法。

此功能基于JSR 269 ,在Sun JDK 6中提供了支持,在Annotation Processing进行后,再次进入Parse and Enter步骤。

3. 语义分析和生成class文件(Analyse and Generate)

Analyse步骤基于抽象语法树进行一系列的语义分析,包括将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起;检查变量使用前是否已声明;推导泛型方法的类型参数;检查类型匹配性;进行常量折叠;检查所有语句都可到达;检查所有checked exception都被捕获或抛出;检查变量的确定性赋值(例如有返回值的方法必须确定有返回值);检查变量的确定性不重复赋值(例如声明为final的变量等);解除语法糖(消除if(false) {…} 形式的无用代码;将泛型Java转为普通Java;将含有语法糖的语法树改为含有简单语言结构的语法树,例如foreach循环、自动装箱/拆箱等)等。

在完成了语义分析后,开始生成class文件(com.sun.tools.javac.jvm.Gen),生成的步骤为:首先将实例成员初始化器收集到构造器中,将静态成员初始化器收集为();接着将抽象语法树生成字节码,采用的方法为后序遍历语法树,并进行最后的少量代码转换(例如String相加转变为StringBuilder操作);最后从符号表生成class文件。

上面简单介绍了基于javac如何将java源码编译为class文件 ,除javac外,还可通过ECJ(Eclipse Compiler for Java) 或Jikes 等编译器来将Java源码编译为class文件。

class文件中并不仅仅存放了字节码,还存放了很多辅助jvm来执行class的附加信息,一个class文件包含了以下信息。

结构信息

包括class文件格式版本号及各部分的数量与大小的信息。

元数据

简单来说,可以认为元数据对应的就是Java源码中"声明"与"常量"的信息,主要有:类/继承的超类/实现的接口的声明信息、域(Field)与方法声明信息和常量池。

方法信息

简单来说,可以认为方法信息对应的就是Java源码中"语句"与"表达式"对应的信息,主要有:字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试用符号信息。

以一段简单的代码来说明class文件格式。

  1. public class Foo{  
  2.     private static final int MAX_COUNT=1000;  
  3.     private static int count=0;  
  4.     public int bar() throws Exception{  
  5.         if(++count >= MAX_COUNT){  
  6.             count=0;  
  7.             throw new Exception("count overflow");  
  8.         }  
  9.         return count;  
  10.  
  11.     }  

执行javac -g Foo.java(加上-g是为了生成所有的调试信息,包括局部变量名及行号信息,在不加-g的情况下默认只生成行号信息)编译此源码,之后通过javap -c -s -l -verbose Foo来查看编译后的class文件,结合class文件格式来看其中的关键内容。

  1. // 类/继承的超类/实现的接口的声明信息  
  2. public class Foo extends java.lang.Object  
  3.   SourceFile: "Foo.java"  
  4.   // class文件格式版本号,major version: 50表示
    为jdk 6,49为jdk 5,48为jdk 1.4,只有高版本能执行
    低版本的class文件,这也是jdk 5不能执行jdk 6编译的代码的原因。  
  5.   minor version: 0  
  6.   major version: 50  
  7.  // 常量池,存放了所有的Field名称、方法名、方法签名、
    类型名、代码及class文件中的常量值。  
  8.   Constant pool:  
  9. const #1 = Method   #7.#27; //  java/lang/Object."<init>":()V  
  10. const #2 = Field    #6.#28; //  Foo.count:I  
  11. const #3 = class    #29;    //  java/lang/Exception  
  12. const #4 = String   #30;    //  count overflow  
  13. const #5 = Method   #3.#31; //  java/lang/
    Exception."
    <init>":(Ljava/lang/String;)V  
  14. …  
  15. const #34 = Asciz   (Ljava/lang/String;)V;  
  16. {  
  17. // 将符号输入到符号表时生成的默认构造器方法  
  18. public Foo();  
  19.   …  
  20. // bar方法的元数据信息  
  21. public int bar()   throws java.lang.Exception;  
  22.   Signature: ()I  
  23.   // 对应字节码的源码行号信息,可在编译的时候通过
    -g:none去掉行号信息,行号信息对于查找问题而言至关重要,
    因此最好还是保留。  
  24. LineNumberTable:  
  25.    line 9: 0  
  26.    line 10: 15  
  27.    line 11: 19  
  28.    line 13: 29  
  29.   // 局部变量信息,如生成的class文件中无局部变量信息,
    则无法知道局部变量的名称,并且局部变量信息是和方法绑定的,
    接口是没有方法体的,所以ASM之类的在获取接口方法时,
    是拿不到方法中参数的信息的。  
  30. LocalVariableTable:  
  31.    Start  Length  Slot  Name   Signature  
  32.    0      33      0    this       LFoo;  
  33.  
  34.   Code:  
  35.    Stack=3Locals=1Args_size=1 
  36.    // 方法对应的字节码  
  37. 0:  getstatic   #2; //Field count:I  
  38.    ..  
  39.    29:  getstatic   #2; //Field count:I  
  40.    32:  ireturn  
  41.       …  
  42.   // 记录有分支的情况(对应代码中if..、for、while等),
    在下一节"类加载机制"中会讲解这个的作用  
  43. StackMapTable: number_of_entries = 1 
  44.    frame_type = 29 /* same */  
  45.  // 异常处理器表  
  46. Exceptions:  
  47.    throws java.lang.Exception  
  48. ..  

从上可见,class文件是个完整的自描述文件,字节码在其中只占了很小的部分,源码编译为class文件后,即可放入jvm中执行。执行时jvm首先要做的是装载class文件,这个机制通常称为类加载机制。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26246674/viewspace-706703/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 疯狂Java
下一篇: 类加载机制
请登录后发表评论 登录
全部评论

注册时间:2011-09-05

  • 博文量
    8
  • 访问量
    8247