导读:
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
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
本文介绍了开源的Javac 1.7编译器,详细讲述了其开源背景、版本信息,以及作者分析源码的原因。文章提到了分析javac源码需要的基本条件,强调了javac是用Java语言自举编写的。作者还概述了javac的编译流程,包括词法分析、语法分析、符号表、属性分析等多个阶段。最后,简述了javac的安装步骤,提供了一个简化版的源码编译和运行方法。
1400

被折叠的 条评论
为什么被折叠?



