Javac1.7编译器源码分析_简介与安装

导读:
  1.1:简介:
  1.1.1 javac版本
  SUN公司在2006年11月份已将java语言编译器(javac)源码通过GPLv2的方式开源,
  还建立了“the Open-Source JDK Community”,网址是:http://openjdk.java.net/
  截止到写这篇文章时,最新的javac Source Release已到了b13,
  一般是半个月出一个beta版,从b04到b13变化都不大,javac类文件没有添加一个。
  本文以及后续文章都是基于以下版本:
  compiler-7-ea-src-b10-21_mar_2007 (为了方便起见,以后都简写成javac1.7)
  1.1.2 我最初想到要分析javac源码的原因
  a 看编译原理的书理论太多,想找个实际的编译器验证一下理论
  b 之前在JDK1.4下写过一些java程序
  c 对javac感兴趣,想要了解所有细节
  d 要是还想分析HotSpot VM,先看javac有利
  e 偶尔想到要反编译别人的代码
  1.1.3 分析javac源码前最好具备的条件JAVA天堂
  a 编译原理的知识基本上忘记了不要紧,
  但还记得有LL(1)文法、递归下降算法、运算符优先这几个名词
  b 知道基本的java语言语法,
  要是像我一样起初不懂Java 5之后的语言新特性也不要紧
  c 越有耐心越好JAVA天堂
  1.1.4 java语言编译器是用什么语言编写的?
  javac1.7的源代码本身是用java语言写的(用编译原理的术语叫“自举”),
  源代码总行数不到8万行。
  在James Gosling的blog上有一篇文章:
  “Compiler fun” (http://blogs.sun.com/jag/entry/compiler_fun)
  里面有提到最初的java语言编译器是用C语言写成的,不过我不能准确的知道从哪个
  JDK发行版开始正式用java语言重写编译器,我尝试在JDK1.4到JDK1.6的tools.jar文件中
  寻找javac,只能确定在JDK1.4到JDK1.6内嵌的java语言编译器都是java语言写的。JAVA天堂
  JDK1.6中的javac与开源的javac1.7生成的字节码几乎没有差别,这也肯定了一点:
  开源的javac1.7并不是SUN公司为开源社区单独开发的一个版本,
  javac1.7最终也会纳入未来的JDK1.7一起发行。
  在“the Open-Source JDK Community”的邮件列表上也有一封邮件证实了这一点:
  主题:OpenJDk7 opensource compiler final Java 7 compiler?
  URL :https://openjdk.dev.java.net/servlets/ReadMsg?list=compiler-dev&msgNo=270
  1.1.5 javac1.7编译流程概述
  javac1.7的编译流程可以简单分为以下主要阶段:
  (只大概说了一下,还有无数的细节问题会在以后详述,
  有看到很讨厌的名词可以直接跳过这一节)
  1) javac命令行选项及选项参数处理
  这一阶段主要是识别javac命令行选项是否是合法的,选项参数是否正确JAVA天堂
  2) 词法分析
  从头到尾分析源文件的字符流,形成关键字、标识符、数字、运算符等等
  具有独立意义的token序列,并去掉源文件中的空白与注释。
  3) 语法分析
  词法分析在javac1.7中其实是做为语法分析阶段的一个过程(或方法)来
  驱动的,整个语法分析阶段完成的任务就是按照java语言的LL(1)文法
  采用递归下降算法并结合运算符优先规则,对token序列构造一棵
  抽象语法树(abstract syntax tree)
  4) 符号识别(符号表)
  一个包、一个类、一个方法、一个字段都可以抽象成一个符号(symbol),
  不同种类的符号之间可以有包含嵌套关系:
  一个包符号可以包含多个类符号,
  一个类符号可以包含多个方法符号与多个字段符号。
  这一阶段的任务就是识别出各类符号,并对不同种类的符号按
  照包含嵌套关系进行归类,并挂接到抽象语法树对应的结点域。
  5) 注释处理(可选阶段)
  6) 属性分析(或称语义分析)
  这一阶段包含了一个复杂的类型系统,语义分析阶段检查语言规范中
  定义的各类规则
  7) 数据流分析
  这一阶段主要是检查final类型的字段与方法中定义的局部变量是否被赋值
  8) Desugar
  在Java 5之后增加了很多语言新特性,但是JVM规范却没有什么变动,
  JVM指令集也没有增加,所以在这一阶段的任务主要是把采用新语言
  特性写成的源代码自动翻译转换成未使用新特性写成的代码。
  如:泛型类到普通类的转换,enhanced for loop到普通for loop的转换。
  
  9) 代码生成
  构造常量池、筛选指令、生成class文件。
  9) 错误处理:
  错误处理贯穿编译流程的所有阶段
  其他:
  javac1.7中没有使用像Lex、YACC这样的生成器工具,
  词法、语法分析与代码生成全都是手工实现的,具有简单、灵活、高效的
  特点,传统编译原理课本上讲授的知识更具通用性,但过于复杂,效率也是
  个问题。
  另外,可惜的是javac1.7(包括sun公司以往发行的JDK中内置的javac)不是
  一个优化编译器,javac1.7并没有独立的优化阶段,
  散落在其他阶段的“优化”可以省略不计。JAVA天堂
  比如:(例子不考虑人的因素,只考虑编译器的行为):
  java 代码
  package my.test; public class Test {
  Test() {
  int v1=1
  while(v1<10000) {
  int v2=5
  v1=v1+v2*2
  }
  }
  }
  用javac1.7或JDK1.6.0生成的字节码如下(部分内容)
  (用命令行“javap -verbose my.test.Test”查看):
  ---------------------------------------------------
  my.test.Test();
  Code:
  Stack=3, Locals=3, Args_size=1
  0: aload_0
  1: invokespecial #1; //Method java/lang/Object."":()V
  4: iconst_1 //将常量1压入堆栈
  5: istore_1 //将常量1弹出堆栈并存入局部变量v1,
  6: iload_1 //将局部变量v1的值压入堆栈
  7: sipush 10000 //将常量10000压入堆栈
  10: if_icmpge 24 //弹出常量10000,弹出局部变量v1的值,局部变量v1的值>=10000结束循环
  13: iconst_5 //将常量5压入堆栈
  14: istore_2 //将常量5弹出堆栈并存入局部变量v2,
  15: iload_1 //将局部变量v1的值压入堆栈
  16: iload_2 //将局部变量v2的值压入堆栈
  17: iconst_2 //将常量2压入堆栈
  18: imul //弹出常量2,弹出局部变量v2的值,相乘后将结果压入堆栈
  19: iadd //弹出结果,弹出局部变量v1的值,相加后将结果压入堆栈
  20: istore_1 //弹出结果并存入局部变量v1
  21: goto 6 //转到6: iload_1
  24: return
  ---------------------------------------------------JAVA天堂
  理想的优化编译器应该能生成如下类似的代码:
  java 代码
  package my.test; public class Test {
  Test() {
  int v1=1
  while(v1<10000) {
  v1=v1+10//v2=5与v2*2总是不变的,可以合并成10
  }
  }
  }
  JAVA天堂
  更理想的优化编译器应该能生成如下类似的代码:
  java 代码
  package my.test; public class Test {
  Test() {}//局部变量v1没有任何用处,完全可以删除
  }
  关于编译流程简短说明的文档也可参考
  Peter von der Ah(javac编译器的主要开发者,前段时间已离开SUN公司)
  在“the Open-Source JDK Community”的邮件列表上回复的一封邮件
  主题:A set ot tutorials about the compiler
  URL :https://openjdk.dev.java.net/servlets/ReadMsg?list=compiler-dev&msgNo=89
  1.1.6 在没有任何设计文档的前提下如何分析javac1.7源码?
  我只说说我采用的方法(我是第一次分析别人的源代码):
  1) 找到一种最简单的办法完成源代码的第一次编译
  2) 找到第一个开始运行的类文件(也就是找到切入点)
  3) 粗略看一下这个类文件定义了哪些字段,有构造方法的话,
  粗略看一下构造方法中做了哪些初始化工作
  4) 要是定义的字段、构造方法太多,把它们都copy一份,单独放到一个文件JAVA天堂
  5) 找到第一个被运行的方法,
  在方法开头和末尾打上Debug(包装System.out.println()后写成的一个类),
  觉得关键的字段(或局部变量),用自己喜欢的方式也打上Debug,
  如果类文件源码超过200行(javac1.7源码中有许多核心类文件大多超过1000行),
  为了切换方便,把这方法copy一份,单独放到一个文件
  6) 当在一个方法中调用了另一个类的方法时,转到3)
  7) 当一个类文件中定义的方法有85%都已Debug过了,从头到尾细细分析一遍类文件
  8) Debug的输出信息最好重定向到一个自定义的文件
  9) 方法中有复杂算法时最好用笔画在纸上
  10)有很多个方法同时来回调用时,把每个方法按调用的顺序单独打开,对照Debug
  信息一起看(我经常打开5、6个EditPlus窗口实例同时看20几个类文件)JAVA天堂
  11)记住随心所欲地想把一个个类文件不打Debug、不按流程顺序分析,是非常低效
  的一种方法,除非这个类文件很独立,只是简单的字段值存取功能。
  
  1.2:安装
  1.2.1 运行环境
  我的OS是Windows XP
  建议安装JDK1.6,我的JDK版本是
  -------------------------------
  java version "1.6.0-beta2"
  Java(TM) SE Runtime Environment (build 1.6.0-beta2-b86)
  Java HotSpot(TM) Client VM (build 1.6.0-beta2-b86, mixed mode, sharing)
  -------------------------------JAVA天堂
  安装JDK后,请在系统变量Path中加入 %JAVA_HOME%bin,
  其中%JAVA_HOME%是JDK安装目录。
  1.2.2 下载javac1.7源码
  下载地址:
  http://www.java.net/download/openjdk/jdk7/promoted/b10/compiler-7-ea-src-b10-21_mar_2007.zip
  解压后会有一个“compiler”目录,
  如果你习惯使用Ant、NetBeans或其他IDE工具编译源码,请直
  接参考“compilerREADME.html”文件,然后跳到“第1.3节”;
  如果你像我一样不会用(或不想用)IDE或想操控一切,
  请按下面的简单步骤操作:
  1.2.3 下载附件中的"Javac.jar"文件,解压到一个目录(这里以“F:Javac”为例)
  (注:"Javac.jar"文件只包含了“compilersrcshareclasses”目录下的
  两个子目录“com”与“javax”,其他子目录或文件都是我自建的,javac1.7源码中
  还包含了无数的测试用例,我觉得它太繁琐了,就自己一边看源码,一边写自己的
  测试例子,所以我把它省略掉了)
  1.2.4 编译javac1.7源码
  打开一个Dos命令行窗口,切换到“F:Javac”目录
  输入“com”按回车,稍等片刻就可以完成编译JAVA天堂
  1.2.5 用javac1.7编译其他java源文件
  打开一个Dos命令行窗口,切换到“F:Javac”目录
  输入“run”按回车,打开“F:Javacmyout.txt”文件,就可以看到Debug信息。
  在F:Javacrun.bat文件中默认是编译F:JavacbinmysrcmytestTest.java文件
  你可以随意替换成你自己的java源文件

本文转自
http://www.javah.net/GUIbiancheng/20070605/2347.html

你可能感兴趣的:(java)