Java编译过程

前言
Java学习过程中,习惯都用IDE直接运行代码,今天用命令行窗口运行代码,结果又一次出现了以前遇到过的报错。

HelloWorld程序

package com.libao.practice;

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

写好代码后,我将HelloWorld.java文件复制到了桌面,然后执行编译

javac HelloWorld.java

桌面形成HelloWorld.class字节码文件,执行文件

java HelloWorld.class

在这里插入图片描述
原因就在于,执行时不要添加.class,直接用java HelloWorld即可,重新编译运行,但是还是报错
Java编译过程_第1张图片
出现这样的问题,可能有两点,一个是环境变量CLASSPATH没配好,其值应如下:

.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

如果还是出错,可能就是package的导入问题了,我的代码中,包含了package com.libao.practice;,而我将.class文件复制到了桌面,这样包路径就不对了,直接删除这行,然后重新编译执行,就OK了
在这里插入图片描述

javac是什么

javac是一种编译器,将java源代码编译成JVM可以识别的二进制码(字节码),就是将 *.java文件转换为 *.class文件

如何编译的呢?
javac的结构主要分为:词法分析器,语法分析器,语义分析器,代码生成器
Java编译过程_第2张图片
词法分析器:逐行读取源代码,将源代码匹配到Token流,即识别每一个单词是什么东西,比如package匹配到Token.PACKAGE,class匹配到Token.CLASS,类名或者变量名匹配成Token.IDENTIFIER。

语法分析器:主要将Token流转换为更加结构化的语法树,就好像将上边的单词组装成一个完整的句子。

语义分析器:语法树再精简和处理,如添加默认构造函数,检查变量初始化,类型是否匹配,检查exception是否抛出等等

代码生成器:将语法树生成java字节码,可以理解为中间语言。

编译运行的过程
Java编译过程_第3张图片
上图就是Java文件从编码完成到最终执行的整个过程,编译器就是上文提到的javac,而.class文件是在JVM中运行的,由JVM转换成机器码后,计算机再处理机器码,实现程序的运行。

什么是类加载过程
JVM(Java虚拟机)把.class文件中的类信息加载进内存,并进行解析生成对应的class对象的过程。

JVM在运行过程中,遇到某个需要的类时,才会进行加载,且只加载一次,而不是一开始就将所有的类都加载进来。

类加载:
包括以下三个方面:

  • 加载
  • 链接:验证、准备、解析
  • 初始化
    Java编译过程_第4张图片

加载:将.class文件通过类加载器加载到内存

  • 字节码来源:本地路径编译生成的.class文件,jar包中的.class文件,远程网络中的.class文件,动态代理实时编译
  • 类加载器:启动类加载器,扩展类加载器,应用类加载器,自定义类加载器

验证:保证加载的字节流符合虚拟机规范。

  • 文件格式验证
  • 字节码验证
  • 符号引用验证

准备:为类变量分配内存,并赋予初值。初值不是代码中具体的初始值,而是JVM根据不同变量类型的默认初始值,如8中基本类型默认初值为0,引用类型默认初值为null,常量默认初值就是代码中设置的值。

解析:将常量池中的符号引用替换为直接引用的过程。

1.符号引用(Symbolic References):
  
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。
  
例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。
  
在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
  
各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

2.直接引用:
直接引用可以是
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

初始化:对类变量初始化,执行构造器的过程。

  • 只对static修饰的变量或语句进行初始化
  • 若父类未初始化,则优先初始化父类
  • 若包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行

参考:
JVM符号引用于直接引用:https://www.cnblogs.com/shinubi/articles/6116993.html
编译执行过程图:https://www.cnblogs.com/junge110/p/10150597.html
编译原理:https://blog.csdn.net/u013782203/article/details/50949769
类加载过程:
https://blog.csdn.net/u010942465/article/details/81709246
https://blog.csdn.net/ln152315/article/details/79223441

你可能感兴趣的:(Java)