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触发。