加载 -> 连接 (验证 -> 准备 -> 解析) -> 初始化 -> 使用 -> 卸载
加载阶段,jvm 会通过类名获取到此类的字节码文件(.class 文件), 然后将该文件中的数据结构转存到内存里(转化为运行时方法区内的数据结构), 最后在堆中生成一个代表该类的 Class 对象,用于后期使用者创建对象或者调用相关方法。
验证阶段用于保证 Class 文件符合 jvm 规范,如果验证失败会抛出 error。
在该阶段虚拟机会给类对象的静态成员变量配置内存空间,并赋初始值。
将类/接口/字段/方法中的号引用替换为直接引用。
虚拟机会调用类对象的初始化方法来进行类变量的赋值。
必须要有类去主动使用该 Class。 方式有: 使用 new 关键字、反射、克隆、反序列化; 调用类的静态方法; 调用一个类的子类的时候会初始化其父类; 包含 main() 方法的类。 被动使用则不会去装载 Class。 方式有: 调用了其父类的静态方法。
总结:
jvm 秉持了实用主义理念,对于没有用到的 Class 不会进行装载。 但是在 java 代码的启动环节会加载一些使用到的类。
在 jdk8 中用来加载 jvm 自身需要的类,c++ 实现,用来加载 rt.jar。 在 jdk9 之后的 jdk 中,Bootstrap ClassLoader 主要用来加载 java.base 中的核心系统类。
jdk8 中用来加载 ${JAVA_HOME}/lib/ext 目录下的类。 在 jdk9 中已经被移除。
jdk9 之后用来代替 ExtClassLoader 的加载器,用来加载 jdk 中的非核心模块类。
用来加载一般的应用类。
使用者自己定义的,一般继承 java.lang.ClassLoader 的类。
任意一个 ClassLoader 在尝试加载一个类的时候,都会先尝试调用其父类的相关方法去加载类,如果其父类不能加载该类,则交由子类去完成。 这样的好处:对于任意使用者自定义的 ClassLoader,都会先去尝试让 jvm 的 Bootstrap ClassLoader 去尝试加载(自定义的 ClassLoader 都继承了它们)。那么就能保证 jvm 的类会被优先加载,限制了使用者对 jvm 系统的影响。
源码探究使用 jdk11,与 jdk8 中的有些许不同。
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 是 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 是 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 也是 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 的核心方法是 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); } }