手记

深入分析Java Web-04-Javac编译原理

编译过程

我们写的Java代码最终都会被编译成二进制文件class来执行,本篇文章将会讲解Java源代码是如何转化为class字节码的。对Javac的编译过程进行讲解。
Javac编译器的作用就是将符合Java语言规范的源代码转化成符合Java虚拟机规范的Java字节码,而如何实现这个过程,是下面需要讨论的问题。
1.词法分析,我们所写的Java代码,只有符合语法规范才能通过编译。识别if、else、for、while等关键词,识别哪些是关键词,哪些不是。
2.语法分析,判断语法是否符合规范,例如if括号里是不是一个布尔判断表达式。
3.语义分析,将复杂的封装,转变为最基本的语义。例如foreach转for循环结构。
4.代码生成器,通过字节码生成器生成字节码。

为什么不直接写字节码,效率肯定很高?

写代码除了自己能看懂意外,其他人也能快速传阅,做到简单易读,比如下面这份代码:
HelloWorld.java

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello Word!");
	}
}

但是如果换成这个呢?
HelloWorld.class

cafe babe 0000 0034 0022 0700 0201 0023
636f 6d2f 6164 7661 6e63 6564 632f 6872
7379 732f 7574 696c 2f48 656c 6c6f 576f
726c 6407 0004 0100 106a 6176 612f 6c61
6e67 2f4f 626a 6563 7401 0006 3c69 6e69
743e 0100 0328 2956 0100 0443 6f64 650a
0003 0009 0c00 0500 0601 000f 4c69 6e65
4e75 6d62 6572 5461 626c 6501 0012 4c6f
6361 6c56 6172 6961 626c 6554 6162 6c65
0100 0474 6869 7301 0025 4c63 6f6d 2f61
6476 616e 6365 6463 2f68 7273 7973 2f75
7469 6c2f 4865 6c6c 6f57 6f72 6c64 3b01
0004 6d61 696e 0100 1628 5b4c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b29 5609
0011 0013 0700 1201 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0c00 1400 1501
0003 6f75 7401 0015 4c6a 6176 612f 696f
2f50 7269 6e74 5374 7265 616d 3b08 0017
0100 0b48 656c 6c6f 2057 6f72 6421 0a00
1900 1b07 001a 0100 136a 6176 612f 696f
2f50 7269 6e74 5374 7265 616d 0c00 1c00
1d01 0007 7072 696e 746c 6e01 0015 284c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b29 5601 0004 6172 6773 0100 135b 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 0a53 6f75 7263 6546 696c 6501 000f
4865 6c6c 6f57 6f72 6c64 2e6a 6176 6100
2100 0100 0300 0000 0000 0200 0100 0500
0600 0100 0700 0000 2f00 0100 0100 0000
052a b700 08b1 0000 0002 000a 0000 0006
0001 0000 0003 000b 0000 000c 0001 0000
0005 000c 000d 0000 0009 000e 000f 0001
0007 0000 0037 0002 0001 0000 0009 b200
1012 16b6 0018 b100 0000 0200 0a00 0000
0a00 0200 0000 0500 0800 0600 0b00 0000
0c00 0100 0000 0900 1e00 1f00 0000 0100
2000 0000 0200 21

是不是晦涩不易读,而且也不太可能一一背下来使用。所以我们需要使用简单易懂的语法,来实现我们想要的功能。

1.词法分析器

你可以理解为这个分析器就是个jar包,就好像我们用eclipse写Java代码,其实你甚至可以用Java写一个eclipse出来。
Javac的主要词法分析器的接口类是 com.sun.tools.javac.parser.Lexer ,它的默认实现类是 com.sun.tools.javac.parser.Scanner,Scanner 会逐个读取Java源文件的单个字符,然后解析出符合Java语言规范的Token序列。JavacParser 规定了哪些词是符合Java语言规范的,而具体读取和归类不同词法的操作由Scanner完成。Token规定了所有Java语言的合法关键词,Names 用来存储和表示解析后的词法。
词法分析过程是在JavacParser的parseCompilationUnit方法中完成的,这个方法的代码如下:

public JCTree.JCCompilationUnit parseCompilationUnit() {
			int pos = S.pos();
			JCExpression pid = null;
			String dc = S.docComment();
			JCModifiers mods = null;
			List<JCAnnotation> packageAnnotations = List.nil();
			if (S.token() == MONKEYS_AT)
				mods = modifiersOpt () ;//解析修饰符
			if (S • token () == PACKAGE) {//解析 package 声明
				if (mods != null) {
						checkNoMods(mods.flags);
						packageAnnotations = mods.annotations;
						mods = null;
				}
				S.nextToken();
				pid = qualident();
				accept(SEMI);
			}
			ListBuffer<JCTree> defs = new ListBuffer<JCTree>();
			boolean checkForImports = true;
			while (S.token() != EOF) {
			if (S.pos () <= errorEndPos) {
				//跳过错误字符
				skip(checkForlmports, false, false, false);
				if (S.token() == EOF)
					break;
				}
			if (checkForImports && mods == null && S.token() == IMPORT) {
				defs . append (importDeclaration () ) ; //解析 import 声明
			} else {//解析class类主体
				JCTree def = typeDeclaration(mods);
				if (keepDocComments && dc ! = null && docComments. get (def)==dc) {
					//如果在前面的类型声明中已经解析过了,那么在top level中将不
					//再重复解析
					dc =null;
			}
			if (def instanceof JCExpressionStatement)
				def = ((JCExpressionStatement)def).expr;
				defs.append(def);
			if (def instanceof JCClassDecl)
				checkForImports = false;
				mods = null;
			}
			}
			JCTree.JCCompilationUnit toplevel = F.at (pos) .TopLevel(packageAnnotations, pid, defs.toList());
			attach(toplevel, dc);
			if (defs.elems.isEmpty())
				storeEnd(toplevel, S.prevEndPos());
			if (keepDocComments)
				toplevel.docComments = docComments;
			if (keepLineMap)
				toplevel.lineMap = S.getLineMap();
				return toplevel;
			}

从源文件的一个字符开始,按照Java语法规范依次找出package、import、类定义,以及属性和方法定义等,最后构建一个抽象语法树。

2.语法分析器

语法分析器是将词法分析器分析的Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。哪些词语组合在一起是主语、哪些是谓语、哪些是宾语、哪些是定语等,要做进一步区分。

3.语义分析器

对语义进行检查,如给类添加默认的构造函数,检查变量在使用前是否已经初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有的操作语句是否可达,检査checked exception异常是否已经捕获或抛出,解除Java的语法糖,等等。当所有这些操作完成后就可以按照这棵完整的语法树生成我们想要的Java字节码了。

4.代码生成器

生成Java字节码需要经过以下两个步骤。
(1 )将Java方法中的代码块转化成符合JVM语法的命令形式,JVM的操作都是基于
栈的,所有的操作都必须经过出栈和进栈来完成。
( 2 )按照JVM的文件组织格式将字节码输出到以class为扩展名的文件中。

总结

2、3、4我还没来得及仔细咀嚼,暂时先这样,等我消化消化先。
如果你对Java感兴趣,想深入理解Java原理,可以交流交流

0人推荐
随时随地看视频
慕课网APP