類載入器ClassLoader
上篇文章說到,Class類可以通過一個類的全限定名去載入類,那麼底層是如何去載入的呢?這就是我們今天要聊的類載入器ClassLoader,其可以通過一個類的全限定名來獲取描述此類的二進位制位元組流,也即是將編譯過後的Class檔案載入到記憶體中。
需要注意的是,即使是同一個類,類載入器不一樣,就必定不相等。
例如自定義了一個類載入器跟JVM預設載入器進行比對
/** *自定義類載入器 */ class MyClassLoader extends ClassLoader { //類載入需要用到包名 String packageName; public MyClassLoader(String packageName) throws ClassNotFoundException {this.packageName = packageName; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { String filename = name + ".class"; name = packageName+"."+name.substring(name.lastIndexOf("/")+1); InputStream is = null; try {is = new FileInputStream(new File(filename)); } catch (FileNotFoundException e) { e.printStackTrace(); } if (is == null) { return super.findClass(name); } try { byte[] bytes = new byte[is.available()]; is.read(bytes);//根據class對應的二進位制檔案,呼叫defineClass return defineClass(name,bytes,0,bytes.length); } catch (IOException e) { e.printStackTrace(); return null; } } }
//引數是包名,類載入需要用到包名 MyClassLoader myClassLoader = new MyClassLoader("com.liusy.lang"); //引數是java檔案編譯過後的全路徑 Object obj = myClassLoader.loadClass("H:/Code/IDEACODE/java_source_code/out/production/java_source_code/com/liusy/lang/ClassSource"); System.out.println(obj);System.out.println(obj instanceof ClassSource);
上述程式碼執行結果如下
Java的3種類載入器
1、Bootstrap ClassLoader,頂級載入器。
啟動類載入器,載入$JAVA_HOME$/jre/lib下的核心類庫,也是所有載入器的頂級父類,由c++所寫。也可以用JVM引數-Xbootclasspath指定其載入的目錄。
//檢視其載入的jar包資訊 Launcher.getBootstrapClassPath().getURLs()
2、Extension ClassLoader,擴充套件類載入器
負責載入$JAVA_HOME$/jre/lib/ext目錄中的jar檔案,是Application ClassLoader的父類。
//檢視其載入的jar包資訊 URL[] urLs = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
3、Application ClassLoader,應用程式類載入器
系統預設載入器,負責載入使用者類所在路徑的類資訊。可以由ClassLoader.getSystemClassLoader()直接獲取。
下圖是獲取系統類載入器以及獲取其父類,可以看到,AppClassLoader的父類就是ExtClassLoader,而ExtClassLoader的父類是null,這是因為頂級載入器BootstrapClassLoader是用C++所寫,java無法獲取其資訊。
JVM的雙親委派模型(保證父類載入器會先載入類)
工作流程:如果一個類載入器收到了類載入的請求,首先不會自己去嘗試載入此類,而是把請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求都會傳送到頂層的啟動類載入器中,只有當父載入器反饋無法完成此類的載入請求時,子載入器才會嘗試自己去載入。
就是預設載入器不會一開始就去載入,一直往上拋,拋到最頂層,如果到最頂層了,此時又不能載入類,就會往下拋,直到載入完為止。
具體如下圖
這個雙親委派特性體現在ClassLoader類的loadClass方法中
//name:類的全限定名 //resolve:是否連結到指定的類 protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { //檢視是否已被載入,如果是,則直接返回 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //parent是父載入器,也就是一直往上拋 if (parent != null) { c = parent.loadClass(name, false); } else { //沒有父載入器,直接找頂級載入器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { long t1 = System.nanoTime(); //父類載入器無法載入的時候 //用自定義載入器去載入 c = findClass(name); } } if (resolve) { //連結到指定的類 resolveClass(c); } return c; } }
上面的loadClass方法開頭有一個加鎖的程式碼,加鎖的物件的getClassLoadingLock是這個方法返回的。
protected Object getClassLoadingLock(String className) { Object lock = this; //parallelLockMap是一個Map物件 //parallelLockMap為空,則直接返回當前ClassLoader if (parallelLockMap != null) { Object newLock = new Object(); //className作為key,如果存在,則直接返回舊值,如果不存在, //則將newLock作為value存入,此時lock就是newLock lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }
而parallelLockMap 是什麼東西呢?如下,是一個ConcurrentHashMap物件,文件顯示是該類不為null的時候當前載入器就具有並行的功能。
private final ConcurrentHashMap<String, Object> parallelLockMap; private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; //ParallelLoaders是靜態內部類 if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; }}
private static class ParallelLoaders { private ParallelLoaders() {} //擁有並行能力的set集合 private static final Set<Class<? extends ClassLoader>> loaderTypes = Collections.newSetFromMap( new WeakHashMap<Class<? extends ClassLoader>, Boolean>()); static { synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); } } //往set集合上新增擁有並行能力的ClassLoader static boolean register(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { if (loaderTypes.contains(c.getSuperclass())) { loaderTypes.add(c); return true; } else { return false; } } } //判斷某個ClassLoader是否擁有並行能力 static boolean isRegistered(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { return loaderTypes.contains(c); } } }
另外,自定義類載入器官方推薦是重寫findClass()方法,這樣可以確保是符合雙親委派模型的。
=======================================================
我是Liusy,一個喜歡健身的程式設計師。
歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。