JVM学习总结——为啥将 .java 文件编译为.class文件?

时间:2024-03-31 15:04:54

百度百科解释:

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

一句话:JVM只能加载符合规范的字节码文件(.class)。

Java之所以能跨平台,是因为JVM能跨平台。JVM怎么跨平台,不同平台装不同的JVM。(java源代码(符合语言规范)-->javac-->.class(二进制文件)-->jvm-->机器语言(不同平台不同种类))

何如将.java文件编译成.class文件。

这需要结合源码分析:

首先下载源码

放入一张经典图片

JVM学习总结——为啥将 .java 文件编译为.class文件?

对应源码分析:

入口:com.sun.tools.javac.main.JavaCompiler 类 compile 方法
public void compile(List<JavaFileObject> sourceFileObjects,List<String> classnames,Iterable<? extends Processor> processors)


图中 parseFiles 方法功能
1.将 .java内容读取到内存中 readSource(filename))
 parseFiles方法中trees.append(parse(fileObject));
 parse方法中JCTree.JCCompilationUnit t = parse(filename, readSource(filename));


2.词法分析:(将源码代码的字符流转变为标记(Token)集合例如:int a=b+2 收集6个标记 int,a,=,b,+,2) 
protected JCCompilationUnit parse(JavaFileObject filename, CharSequence content);
Parser parser = parserFactory.newParser(content, keepComments(), genEndPos, lineDebugInfo);

3.语法分析:(将 词法分析的标记(Token)集合 构造成 抽象语法树。 )
抽象语法树,用树形描述程序代码语法结构,语法树每个节点都是一个语法结构(包括 包,类型,修饰符,运算符,接口,返回值,代码注释等)
tree = parser.parseCompilationUnit();
com.sun.tools.javac.parser.JavacParser类 parseCompilationUnit 方法
//抽象语法树
JCTree def = typeDeclaration(mods, docComment);

图中 enterTrees方法功能
使用 enterTrees填充符号表
符号表示一组符号地址和符号信息构成的表格。
符号表编译不同阶段都有用到,例如语义分析 检测使用变量名称类型是否和声明类型一致。
enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))),
enterTrees 中 enter.main(roots) 填充符号;

图中processAnnotations方法功能
注解处理器
 initProcessAnnotations(processors);//初始化
 processAnnotations()//执行过程。
 注解中如果对抽象语法数任意元素修改 编译器将重新执行词法和语法和填充符号。直到没有变化为止。


图中delegateCompiler.compile2();方法功能

语义检查和字节码生成。
1.标注检查。
2.数据和控制流分析。
3.解析语法糖。
4.字节码生成。

图中attribute方法功能
检查的内容包括:变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。
标注检查步骤在Javac源码中的实现类是com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类。

图中flow方法功能
数据及控制流分析是对程序上下文逻辑更进一步的验证,它可以检查:程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等。
编译时期的数据及控制流分析与类加载时的数据及控制流分析的目的基本上是一致的,但校验范围有所区别,有一些校验项只有在编译期或运行期才能进行。
局部变量与字段(实例变量、类变量)是有区别的,它在常量池中没有CONSTANT_Fieldref_info的符号引用,自然就没有访问标志(Access_Flags)的信息,甚至可能连名称都不会保留下来(取决于编译时的选项),自然在Class文件中不可能知道一个局部变量是不是声明为final了。因此,将局部变量声明为final,对运行期是没有影响的,变量的不变性仅仅由编译器在编译期间保障。
在Javac的源码中,数据及控制流分析的入口是flow()方法,具体操作由com.sun.tools.javac.comp.Flow类完成。

图中desugar方法功能
语法糖(Syntactic Sugar),也称糖衣语法。指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
Java中最常用的语法糖:泛型、变长参数、自动装箱/拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖。

 

图中generate方法功能
字节码生成阶段将语法树,符号表转换为字节码写到磁盘同时还进行了少量代码添加和转换工作。

com.sun.tools.javac.main.JavaCompiler 类 generate方法产生 .class文件
com.sun.tools.javac.jvm.ClassFile
存放
public final static int JAVA_MAGIC = 0xCAFEBABE;
Version Enum
V45_3(45, 3), // base level for all attributes
V49(49, 0),   // JDK 1.5: enum, generics, annotations
V50(50, 0),   // JDK 1.6: stackmaps
V51(51, 0),   // JDK 1.7
V52(52, 0);   // JDK 1.8: lambda, type annos, param names
com.sun.tools.javac.jvm.ClassWriter 类 writeClass方法将编译好的.java 写到out文件中
poolbuf.appendInt(JAVA_MAGIC);
poolbuf.appendChar(target.minorVersion);
poolbuf.appendChar(target.majorVersion);