加載 -> 鏈接 (驗證 -> 準備 -> 解析) -> 初始化 -> 使用 -> 卸載
加載階段,jvm 會經過類名獲取到此類的字節碼文件(.class 文件), 而後將該文件中的數據結構轉存到內存裏(轉化爲運行時方法區內的數據結構), 最後在堆中生成一個表明該類的 Class 對象,用於後期使用者建立對象或者調用相關方法。
驗證階段用於保證 Class 文件符合 jvm 規範,若是驗證失敗會拋出 error。
在該階段虛擬機會給類對象的靜態成員變量配置內存空間,並賦初始值。
將類/接口/字段/方法中的號引用替換爲直接引用。
虛擬機會調用類對象的初始化方法來進行類變量的賦值。
必需要有類去主動使用該 Class。 方式有: 使用 new 關鍵字、反射、克隆、反序列化; 調用類的靜態方法; 調用一個類的子類的時候會初始化其父類; 包含 main() 方法的類。 被動使用則不會去裝載 Class。 方式有: 調用了其父類的靜態方法。
總結:java
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 中的有些許不一樣。c++
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 字節碼的方法:app
// 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 類:jvm
// 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:ide
// 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(...) 方法。這種方式下就完成類加載器的雙親委派機制:ui
// 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); } }