Java应用程序启动过程
public class HelloWorld {
public void sayHello(){
System.out.println("hello world!");
}
public static void main(String[] args){
new HelloWorld().sayHello();
}
}
复制代码
当我们使用java HelloWorld运行以上程序时,发生了什么?

创建引导类加载器
在Windows环境下调用jvm.dll库创建BootstrapClassLoader引导类加载器,这是一个C++实例(主要用来加载jdk/jre/lib目录下的jar类库,也称VM内置类加载器),所以如果你妄图通过Java程序打印它会发现结果是null:
public class BootstrapClassLoaderPrintLab {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
}
}
复制代码
初始化系统类加载器
在运行时启动序列中,会触发ClassLoader#initSystemClassLoader的首次调用:
private static ClassLoader scl;
private static boolean sclSet;
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
return scl;
}
// is typically the class loader used to start the application.
// This method is first invoked early in the runtime's startup sequence
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
scl = l.getClassLoader();
sclSet = true;
}
}
复制代码
接着sun.misc.Launcher#getLauncher会被调用:
private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}
public ClassLoader getClassLoader() {
return this.loader;
}
复制代码
在Launcher构造方法中,创建了ExtClassLoader扩展类加载器和AppClassLoader应用程序类加载器,后者通过构造方法传参保留了前者的引用,跟踪调用链不难发现后者通过ClassLoader parent属性保留了对前者的委托引用。
小结:
ClassLoader#scl和Launcher#loader保存了应用程序类加载器实例的引用
加载应用程序启动类
通过ClassLoader#initSystemClassLoader获取已初始化好的系统类加载器,此刻也即AppClassLoader,调用其loadClass方法对HelloWorld.class进行加载:
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
// 忽略校验检查相关代码
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
复制代码
通过断点调试(debug condition: var1.equals("HelloWorld"))发现其委托父类URLClassLoader处理,并继续委托父类ClassLoader处理:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
复制代码
查看其方法注释:
加载具有指定二进制名称的类。 此方法的默认实现按以下顺序搜索类:1.调用
findLoadedClass(String)以检查类是否已加载。2.在父类加载器上调用loadClass方法。 如果parent为null,则使用虚拟机内置的类加载器。3.调用findClass(String)方法来查找类
如此类加载器的层次结构以及双亲委派机制逐步浮出水面:

JDK通过
ClassLoader#loadClass内置了双亲委派机制;鼓励开发者重写ClassLoader#findClass来实现自己的类资源查找逻辑
类加载过程:
- 加载:通过类全限定名定位其二进制字节流资源位置,并读取到本地内存字节数组中
- 验证:二进制字节流格式需符合Java虚拟机规范,例如开头为
CAFEBABE、各元数据段字节排列是否正确(如常量池元数据段分为常量池个数和常量实体数组两部分,个数需和数组大小对应;常量实体应以常量内容及其对应字节长度等)。类似于协议报文内容编排需要符合对应协议规定的报文格式要求。 - 解析:静态链接,例如将
final/static方法的符号引用解析为直接引用(代码段地址) - 准备:例如将类变量赋予类型对应的默认值
- 初始化:例如执行静态变量的显式赋值、静态代码块
类被加载到方法区后主要包括以下信息:
- 运行时常量池
- 类型信息:例如类名、类访问修饰符、是否
final、继承/实现了哪些类 - 字段信息:字段类型、字段名称、字段访问修饰符、
final、static - 方法信息:返回值类型、方法名称、参数类型列表、访问修饰符、
final、static - 加载该类的类加载器对象引用
- Class对象引用:在类被加载到方法区后,会在堆中相应创建一个Class对象作为应用程序访问该类元数据的入口
执行main方法
由JVM触发。





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)