在編寫Java程序時須要使用javac命令將.java後綴名的文件編譯成.class文件,而後JVM經過執行.class文件來運行咱們寫的程序,那麼JVM怎麼才能執行.class文件呢?
這就須要類加載器了。java
在jdk中找到sun.misc.Launcher文件,這個文件是JVM中的啓動器實例,在該類內部有getLauncher()
方法app
public static Launcher getLauncher() { return launcher; }
它返回的是launcher
實例ide
private static Launcher launcher = new Launcher();
在構造方法調用時會初始化 ExtClassLoader和AppClassLoader(BootstrapClassLoader會在Java虛擬機建立以後進行加載,因此是在此以前)。測試
public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); // 非核心的就去掉了 }
經過點擊 getXxxClassLoader()
跟到最後能夠看到this
private ClassLoader(Void unused, ClassLoader parent) { // 經過這行可讓加載器之間創建父子級關係 this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); assertionLock = this; } }
注意: 咱們查看ExtClassLoader時會發現這裏的parent是null,而查看AppClassLoader時傳入的parent是ExtClassLoader的對象實例。因此這裏也就印證了上面畫的圖。spa
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 檢查目標類是否已加載,若是已加載則返回 Class<?> c = findLoadedClass(name); // 沒有加載到 if (c == null) { long t0 = System.nanoTime(); try { // 當前加載器的父加載器是否存在 if (parent != null) { // 若是父加載器存在則使用父加載器進行加載 c = parent.loadClass(name, false); } else { // 若是父加載器不存在就是用BootstrapClassLoader加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若是父加載器和BootstrapClassLoader都沒有加載到的話 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 本身進行加載 // 這個方法默認是一個空實現,須要本身重寫 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } // 返回已加載到的類或null return c; } }
加載步驟:3d
parent == null
,這時會使用 BootstrapClassLoader 加載findClass()
方法來加載目標類。找到某個ClassLoader,這裏以AppClassLoader爲例code
static class AppClassLoader extends URLClassLoader {
在AppClassLoader中搜索findClass()
方法是找不到的,因此找到每一個加載器共同的父類URLClassLoader
,在這裏能夠看到findClass()
方法。對象
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res != null) { try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
findClass
方法的做用是從指定的路徑找到傳入的文件名稱的.class文件,並返回回去,該方法的主要邏輯在run()
方法中。blog
從前面能夠知道,要想實現classLoader只須要繼承ClassLoader類並重寫 findClass
和 loadClass
方法便可。
在桌面建立了一個名爲Test的Java文件,並使用javac命令進行編譯。
public class Test { public void sayHi() { System.out.println("hi!"); } public static void main(String[] args) { System.out.println("Hello World!"); } }
/** * Created by luyi on 2021/2/16. * 描述:自定義ClassLoader */ public class MyClassLoader extends ClassLoader { public String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } /** * 經過輸入流讀取文件 * * @param name 文件全限定類名 */ public byte[] loadByte(String name) throws IOException { if (name == null || name.length() == 0) { return new byte[1024]; } name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } /** * 將指定的路徑中的指定的文件進行加載 * * @param name 全限定類名 * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (name == null || name.length() == 0) { return null; } try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(); } } public static void main(String[] args) throws Exception { MyClassLoader loader = new MyClassLoader("/Users/luyi/Desktop"); Class<?> clazz = loader.loadClass("Test", false); Object instance = clazz.newInstance(); Method sayHi = clazz.getDeclaredMethod("sayHi", null); sayHi.invoke(instance, null); System.out.println(clazz.getClassLoader().getClass().getName()); } }
運行結果:
從運行結果能夠看出,自定義的類加載器已經完成了對指定文件的加載,並正確的執行了方法。