ClassLoader(一)- 介绍
本文源代碼在Github。
本文僅為個人筆記,不應作為權威參考。
原文
什么是ClassLoader
javadoc ClassLoader:
A class loader is an object that is responsible for loading classes....
Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.
簡單來說:
三種ClassLoader實現
講到bootstrap class loader就不得不說三種常見的ClassLoader實現。
執行下面代碼會看到三種類型的ClassLoader實現:
import com.sun.javafx.util.Logging; import java.util.ArrayList; public class PrintClassLoader {public static void main(String[] args) {System.out.println("Classloader for ArrayList: " + ArrayList.class.getClassLoader());System.out.println("Classloader for Logging: " + Logging.class.getClassLoader());System.out.println("Classloader for this class: " + PrintClassLoader.class.getClassLoader());} }結果如下:
Classloader for ArrayList: null Classloader for Logging: sun.misc.Launcher$ExtClassLoader@5e2de80c Classloader for this class: sun.misc.Launcher$AppClassLoader@18b4aac2- Bootstrap class loader。bootstrap class loader是native code寫的。它是所有ClassLoader的祖先,它是頂級ClassLoader。它負責加載JDK的內部類型,一般來說就是位于$JAVA_HOME/jre/lib下的核心庫和rt.jar。
- Extension class loader。即Extension class loader,負責加載Java核心類的擴展,加載$JAVA_HOME/lib/ext目錄和System Property java.ext.dirs所指定目錄下的類(見Java Extension Mechanism Architecture)。
- System class loader,又稱Application class loader。它的parent class loader是extension class loader(可以從sun.misc.Launcher的構造函數里看到),負責加載CLASSPATH環境變量、-classpath/-cp啟動參數指定路徑下的類。
類的ClassLoader
每個Class對象引用了當初加載自己的ClassLoader(javadoc ClassLoader):
Every Class object contains a reference to the ClassLoader that defined it.其實Class對象的getClassLoader()方法就能夠得到這個ClassLoader,并且說了如果該方法返回空,則說明此Class對象是被bootstrap class loader加載的,見getClassLoader() javadoc:
Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.數組類的ClassLoader
Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.簡單來說說了三點:
下面是一段實驗代碼:
import com.sun.javafx.util.Logging; public class PrintArrayClassLoader {public static void main(String[] args) {System.out.println("ClassLoader for int[]: " + new int[0].getClass().getClassLoader());System.out.println("ClassLoader for string[]: " + new String[0].getClass().getClassLoader());System.out.println("ClassLoader for Logging[]: " + new Logging[0].getClass().getClassLoader());System.out.println("ClassLoader for this class[]: " + new PrintArrayClassLoader[0].getClass().getClassLoader());} }得到的結果如下,符合上面的說法:
ClassLoader for int[]: null ClassLoader for string[]: null ClassLoader for Logging[]: sun.misc.Launcher$ExtClassLoader@5e2de80c ClassLoader for this class[]: sun.misc.Launcher$AppClassLoader@18b4aac2那如果是二維數組會怎樣呢?下面是實驗代碼:
import com.sun.javafx.util.Logging; public class PrintArrayArrayClassLoader {public static void main(String[] args) {System.out.println("ClassLoader for int[][]: " + new int[0][].getClass().getClassLoader());System.out.println("ClassLoader for string[][]: " + new String[0][].getClass().getClassLoader());System.out.println("ClassLoader for Logging[][]: " + new Logging[0][].getClass().getClassLoader());System.out.println("ClassLoader for this class[][]: " + new PrintArrayClassLoader[0][].getClass().getClassLoader());System.out.println("ClassLoader for this Object[][] of this class[]: " + new Object[][]{new PrintArrayArrayClassLoader[0]}.getClass().getClassLoader());} }結果是:
ClassLoader for int[][]: null ClassLoader for string[][]: null ClassLoader for Logging[][]: sun.misc.Launcher$ExtClassLoader@5e2de80c ClassLoader for this class[][]: sun.misc.Launcher$AppClassLoader@18b4aac2 ClassLoader for this Object[][] of this class[]: null注意第四行的結果,我們構建了一個Object[][],里面放的是PrintArrayArrayClassLoader[],但結果依然是null。所以:
ClassLoader類的ClassLoader
ClassLoader本身也是類,那么是誰加載它們的呢?實際上ClassLoader類的ClassLoader就是bootstrap class loader。下面是實驗代碼:
import com.sun.javafx.util.Logging; public class PrintClassLoaderClassLoader {public static void main(String[] args) {// Launcher$ExtClassLoaderSystem.out.println("ClassLoader for Logging's ClassLoader: " + Logging.class.getClassLoader().getClass().getClassLoader());// Launcher$AppClassLoaderSystem.out.println("ClassLoader for this class's ClassLoader: " + PrintClassLoaderClassLoader.class.getClassLoader().getClass().getClassLoader());// 自定義ClassLoaderSystem.out.println("ClassLoader for custom ClassLoader: " + DummyClassLoader.class.getClassLoader().getClass().getClassLoader());}public static class DummyClassLoader extends ClassLoader {} }結果是:
ClassLoader for Logging's ClassLoader: null ClassLoader for this class's ClassLoader: null ClassLoader for custom ClassLoader: nullClassLoader解決了什么問題
簡單來說ClassLoader就是解決類加載問題的,當然這是一句廢話。JDK里的ClassLoader是一個抽象類,這樣做的目的是能夠讓應用開發者定制自己的ClassLoader實現(比如添加解密/加密)、動態插入字節碼等,我認為這才是ClassLoader存在的最大意義。
ClassLoader的工作原理
還是看javadoc的說法:
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.簡單來說:
ContextClassLoader
ClassLoader的委托模型存在這么一個問題:子ClassLoader能夠看見父ClassLoader所加載的類,而父ClassLoader看不到子ClassLoader所加載的類。
這個問題出現在Java提供的SPI上,簡單舉例說明:
這是因為B一般都是在Classpath中的,它是被System class loader加載的,而SPI A是在核心庫里的,它是被bootstrap class loader加載的,而bootstrap class loader是頂級ClassLoader,它不能向下委托給System class loader,所以SPI A是找不到實現B的。
這個時候可以通過java.lang.Thread#getContextClassLoader()和java.lang.Thread#setContextClassLoader來讓SPI A加載到B。
為何SPI A不直接使用System class loader來加載呢?我想這是因為如果寫死了System class loader那就缺少靈活性的關系吧。
Class的唯一性
如果一個類被一個ClassLoader加載兩次,那么兩次的結果應該是一致的,并且這個加載過程是線程安全的,見ClassLoader.java源碼:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {// ...try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.// ...c = findClass(name);// ...}}// ...return c;} }如果一個類被兩個不同的ClassLoader加載會怎樣呢?看下面代碼:
// 把這個項目打包然后放到/tmp目錄下 public class ClassUniqueness {public static void main(String[] args) throws Exception {Class<?> fooClass1 = Class.forName("me.chanjar.javarelearn.classloader.ClassUniqueness");System.out.println("1st ClassUniqueness's ClassLoader: " + fooClass1.getClassLoader());// 故意將parent class loader設置為null,否則就是SystemClassLoader(即ApplicationClassLoader)URLClassLoader ucl = new URLClassLoader(new URL[] { new URL("file:///tmp/classloader.jar") }, null);Class<?> fooClass2 = ucl.loadClass("me.chanjar.javarelearn.classloader.ClassUniqueness");System.out.println("2nd ClassUniqueness's ClassLoader: " + fooClass2.getClassLoader());System.out.println("Two ClassUniqueness class equals? " + fooClass1.equals(fooClass2));}}運行結果是:
1st ClassUniqueness's ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2 2nd ClassUniqueness's ClassLoader: java.net.URLClassLoader@66d3c617 Two ClassUniqueness class equals? false```觀察到兩點:
由此可以得出結論:一個Class的唯一性不僅僅是其全限定名(Fully-qualified-name),而是由【加載其的ClassLoader + 其全限定名】聯合保證唯一。
這種機制對于解決諸如類沖突問題非常有用,類沖突問題就是在運行時存在同一個類的兩個不同版本,同時代碼里又都需要使用這兩個不同版本的類。解決這個問題的思路就是使用不同的ClassLoader加載這兩個版本的類。事實上OSGi或者Web容器就是這樣做的(它們不是嚴格遵照委托模型,而是先自己找,找不到了再委托給parent ClassLoader)。
參考文檔
- JDK Javadoc - ClassLoader
- JDK Javadoc - Class
- Java虛擬機是如何加載Java類的?(極客時間專欄,需付費購買)
- Class Loaders in Java
- 深入探討Java類加載器
- Java Language Specification - Chapter 12. Execution
- Java Virtual Machine Specification - Chapter 5. Loading, Linking, and Initializing
廣告
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的ClassLoader(一)- 介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】】Vue项目部署tomcat,刷新
- 下一篇: MaxCompute,基于Serverl