运行Java程序
还记得刚开始学习Java的时候,首先学的就是如何在文本编辑器里面写Java代码,然后编译和运行。
- 新建Hello.java文件,然后用记事本打开,写上以下代码并保存。
public class Hello {
public static void main(String[] args) {
System.out.println("hello java");
}
}
复制代码
- 打开控制台,定位到Hello.java文件所在目录 cd /Users/stardust/Desktop/TestCode
- 使用命令 javac -d . Hello.java进行编译,这时候会多出一个文件Hello.class
- 使用命令 java Hello 就能在控制台中打印hello java
这大概就是最初的时光了。为何这样就可以运行代码,里面的机制是怎么样的?现在我们来一探究竟。
类加载过程
当我们使用java命令来运行某个类的main方法启动程序时,首先得启动一个JVM容器,然后把这个类的字节码文件加载到JVM中。
Java的底层其实是C++实现的,所以当我们输入java命令的时候,会调用底层的jvm.dll来创建Java核心虚拟机(JVM),接着还会创建一个根(引导)类加载器实例。引导类加载器会加载JDK中的sun.misc.Launcher类并调用其中的getLauncher()方法获取Launcher实例。通过Launcher实例可以获取到AppClassLoader实例。通过AppClassLoader去载入Hello.class。加载完成后,JVM会执行类中的main方法。程序结束后,JVM会自行销毁。如下图所示:
需要注意的是:
- Launcher类实现了饿汉式的单例模式,所以每个JVM只有一个Launcher实例。
- sun.misc.Launcher#getClassLoader返回的是AppClassLoader实例,Launcher类里面有sun.misc.Launcher.AppClassLoader和sun.misc.Launcher.ExtClassLoader两种加载器,逻辑上有父子关系,但是ExtClassLoader并不是AppClassLoader的父类。事实上这两个类加载器都是继承了java.net.URLClassLoader
loadClass加载类的步骤
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这3个步骤,所以有时也把这3个步骤统称为类加载或类初始化。
类的加载
类加载指的是将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM 提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。
- 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
- 从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
- 通过网络加载class文件。
- 把一个Java源文件动态编译,并执行加载。类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
类的连接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
- (1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
- (2)准备:类准备阶段则负责为类的静态Field分配内存,并设置默认初始值。
- (3)解析:将类的二进制数据中的符号引用替换成直接引用。
类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:① 声明静态Field时指定初始值;② 使用静态初始化块为静态Field指定初始值。
JVM初始化一个类包含如下几个步骤。
- (1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
- (2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。
- (3)假如类中有初始化语句,则系统依次执行这些初始化语句。
当执行第2个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;如果该直接父类又有直接父类,则系统再次重复这 3个步骤来先初始化这个父类……依此类推,所以JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都会被初始化。