Class 文件的装载流程 (类加载过程)
加载 -> 连接 (验证 -> 准备 -> 解析) -> 初始化 -> 使用 -> 卸载
加载
加载阶段,jvm 会通过类名获取到此类的字节码文件 (.class 文件),然后将该文件中的数据结构转存到内存里 (转化为运行时方法区内的数据结构),最后在堆中生成一个代表该类的 Class 对象,用于后期使用者创建对象或者调用相关方法。
验证
验证阶段用于保证 Class 文件符合 jvm 规范,如果验证失败会抛出 error。
准备
在该阶段虚拟机会给类对象的静态成员变量配置内存空间,并赋初始值。
解析
将类 / 接口 / 字段 / 方法中的号引用替换为直接引用。
初始化
虚拟机会调用类对象的初始化方法来进行类变量的赋值。
Class 文件被装载的条件
必须要有类去主动使用该 Class。方式有:使用 new 关键字、反射、克隆、反序列化;调用类的静态方法;调用一个类的子类的时候会初始化其父类;包含 main() 方法的类。被动使用则不会去装载 Class。方式有:调用了其父类的静态方法。
总结:
jvm 秉持了实用主义理念,对于没有用到的 Class 不会进行装载。但是在 java 代码的启动环节会加载一些使用到的类。
加载器种类
启动类加载器 (Bootstrap ClassLoader):
在 jdk8 中用来加载 jvm 自身需要的类,c++ 实现,用来加载 rt.jar。在 jdk9 之后的 jdk 中,Bootstrap ClassLoader 主要用来加载 java.base 中的核心系统类。
扩展类加载器 (ExtClassLoader):
jdk8 中用来加载 ${JAVA_HOME}/lib/ext 目录下的类。在 jdk9 中已经被移除。
模块加载器 (PlatformClassLoader):
jdk9 之后用来代替 ExtClassLoader 的加载器,用来加载 jdk 中的非核心模块类。
应用程序类加载器 (AppClassLoader):
用来加载一般的应用类。
自定义加载器:
使用者自己定义的,一般继承 java.lang.ClassLoader 的类。
双亲委派机制
任意一个 ClassLoader 在尝试加载一个类的时候,都会先尝试调用其父类的相关方法去加载类,如果其父类不能加载该类,则交由子类去完成。这样的好处:对于任意使用者自定义的 ClassLoader,都会先去尝试让 jvm 的 Bootstrap ClassLoader 去尝试加载 (自定义的 ClassLoader 都继承了它们)。那么就能保证 jvm 的类会被优先加载,限制了使用者对 jvm 系统的影响。
源码
源码探究使用 jdk11,与 jdk8 中的有些许不同。
ClassLoader
ClassLoader 是类加载器的顶级父类,其核心的方法主要是 loadClass(…) 方法:
// ClassLoader.class
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
// 加锁,保证线程安全
synchronized (getClassLoadingLock(name)) {
// 先去找一次 class 是否已经被加载了,如果已经被加载了就不用重复加载了
// 此方法的核心逻辑由 c++ 实现
Class<?> c = findLoadedClass(name);
// 没有被加载的情况
if (c == null) {long t0 = System.nanoTime(); // 记录时间
try {
// 此处体现双亲委派机制
// 如果该加载器存在父加载器,就会先去调用父加载器的相关方法
// 如果没有父加载器,就去调用 Bootstrap 加载器
if (parent != null) {c = parent.loadClass(name, false);
} else {
// 调用 BootstrapClassLoader,此方法的核心逻辑是 c++ 实现的
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) { }
// 如果依旧加载不到,那么就说明父加载器仍然加载不到信息
// 那么就需要指定的加载器自己去加载了
if (c == null) {long t1 = System.nanoTime();
// 该加载器加载类文件的核心逻辑
// 该方法在 ClassLoader 中是留空的,需要子类按照自身的逻辑去实现
c = findClass(name);
// 此处做一些信息记录,和主逻辑无关
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 解析 class,也是留空的,需要子类去实现
resolveClass(c);
}
return c;
}
}
BuiltinClassLoader
BuiltinClassLoader 是 jdk9 中代替 URLClassLoader 的加载器,是 PlatformClassLoader 与 AppClassLoader 的父类。其继承了 SecureClassLoader,其核心的方法主要是 loadClassOrNull(…) 方法:
// BuiltinClassLoader.class
// step 1
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{// 复写了 loadClass(...) 方法,但是核心是调用 loadClassOrNull(...)
Class<?> c = loadClassOrNull(cn, resolve);
if (c == null)
throw new ClassNotFoundException(cn);
return c;
}
// step 2
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
// 加锁,保证线程安全
synchronized (getClassLoadingLock(cn)) {
// 先去找一次 class 是否已经被加载了,此方法是 ClassLoader 中的
Class<?> c = findLoadedClass(cn);
if (c == null) {
// 这里会需要去先加载模块信息
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null) {BuiltinClassLoader loader = loadedModule.loader();
if (loader == this) {if (VM.isModuleSystemInited()) {c = findClassInModuleOrNull(loadedModule, cn);
}
} else {c = loader.loadClassOrNull(cn);
}
} else {
// 先调用父加载器的相关方法去加载一次
if (parent != null) {c = parent.loadClassOrNull(cn);
}
// 如果没加载到,则用当前加载器去加载
if (c == null && hasClassPath() && VM.isModuleSystemInited(){// 此方法内会调用到 defineClass(...) 方法去加载类文件
c = findClassOnClassPathOrNull(cn);
}
}
}
// 解析 class
if (resolve && c != null)
resolveClass(c);
return c;
}
}
该加载器中还有一个加载 class 字节码的方法:
// BuiltinClassLoader.class
private Class<?> defineClass(String cn, Resource res) throws IOException{URL url = res.getCodeSourceURL();
// 先解析这个 class 的路径
int pos = cn.lastIndexOf('.');
if (pos != -1) {String pn = cn.substring(0, pos);
Manifest man = res.getManifest();
defineOrCheckPackage(pn, man, url);
}
// 这里会将 class 读取出来成一个 byte[] 字符串,并通过 jvm 的相关方法去加载
ByteBuffer bb = res.getByteBuffer();
if (bb != null) {CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
// 该方法最后会调用 ClassLoader 内的 native 方法
return defineClass(cn, bb, cs);
} else {byte[] b = res.getBytes();
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
// 该方法最后会调用 ClassLoader 内的 native 方法
return defineClass(cn, b, 0, b.length, cs);
}
}
BootClassLoader
BootClassLoader 是 ClassLoaders 的一个静态内部类,虽然它从代码实现上是 BuiltinClassLoader 的子类,但是从功能上说它是 PlatformClassLoader 的 parent 类:
// ClassLoader.class
private static class BootClassLoader extends BuiltinClassLoader {BootClassLoader(URLClassPath bcp) {super(null, null, bcp);
}
// 复写了 BuiltinClassLoader 中的 loadClassOrNull(...) 方法
@Override
protected Class<?> loadClassOrNull(String cn) {return JLA.findBootstrapClassOrNull(this, cn);
}
};
PlatformClassLoader
PlatformClassLoader 也是 ClassLoaders 的一个静态内部类,从功能上说它是 BootClassLoader 的子类,同时也是 AppClassLoader 的 parent 类。PlatformClassLoader 主要用来加载一些 module:
// ClassLoader.class
private static class PlatformClassLoader extends BuiltinClassLoader {
static {if (!ClassLoader.registerAsParallelCapable())
throw new InternalError();}
// 此处会将 BootClassLoader 作为 parent 参数传入进去
PlatformClassLoader(BootClassLoader parent) {super("platform", parent, null);
}
// 加载 module
private Package definePackage(String pn, Module module) {return JLA.definePackage(this, pn, module);
}
}
AppClassLoader
AppClassLoader 的核心方法是 loadClass(…),最终会调用到 BuiltinClassLoader.loadClassOrNull(…) 方法,而此方法内部又会调用到 PlatformClassLoader.loadClass(…) 方法;然后实际上 PlatformClassLoader 内部又会去调用 BootClassLoader 的 loadClassOrNull(…) 方法。这种方式下就完成类加载器的双亲委派机制:
// ClassLoader.class
private static class AppClassLoader extends BuiltinClassLoader {
static {if (!ClassLoader.registerAsParallelCapable())
throw new InternalError();}
final URLClassPath ucp;
// 此处会将 PlatformClassLoader 作为 parent 参数传入进去
AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) {super("app", parent, ucp);
this.ucp = ucp;
}
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{SecurityManager sm = System.getSecurityManager();
if (sm != null) {int i = cn.lastIndexOf('.');
if (i != -1) {sm.checkPackageAccess(cn.substring(0, i));
}
}
// 实际上是调用了 BuiltinClassLoader.loadClassOrNull(...) 方法
return super.loadClass(cn, resolve);
}
@Override
protected PermissionCollection getPermissions(CodeSource cs) {PermissionCollection perms = super.getPermissions(cs);
perms.add(new RuntimePermission("exitVM"));
return perms;
}
void appendToClassPathForInstrumentation(String path) {ucp.addFile(path);
}
private Package definePackage(String pn, Module module) {return JLA.definePackage(this, pn, module);
}
protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {return super.defineOrCheckPackage(pn, man, url);
}
}