根據《深刻理解Java虛擬機》提到「經過一個類的全限定名(packageName.ClassName)來獲取描述此類的二進制字節(class文件字節)這個動做的代碼模塊就叫作類加載器(ClassLoader)」。java
一、一般類加載器的做用是加載資源(字節碼文件)到java虛擬機中,想要在一個jvm 進程中惟一確認一個類,除了類的全限定名外,還須要指定它是由哪一個類加載器加載的。
二、好比咱們的類庫須要經過遠程網絡獲取,能夠經過自定義類加載器從遠程加載字節碼文件。
三、java的字節碼文件很容易反編譯出來,一些核心的代碼不想被反編譯出來,能夠對字節碼進行加密,而後經過自定義的類加載器加載這些字節碼,而後進行解碼返回給虛擬機。
四、好比jvm的熱加載和熱部署等功能也須要自定義類加載器來完成。
.......git
一、Bootstrap ClassLoader : 該加載器是最頂層的類加載器,它是加載放在{Java_home}\lib目錄 或者-Xbootclasspath指定路徑下類庫。github
二、Extension ClassLoader : 該類加載器負載加載{Java_home}/lib\ext目錄 或者System.getenv("java.ext.dirs")系統變量路徑下的類庫。spring
三、Application ClassLoader : 該類加載器加載用戶程序類路徑下的類庫,它是默認的程序的類加載器。api
一、雙親委派機制,雙親委派除了啓動類加載器(Bootstrap ClassLoader)外,其餘的類加載器都應該有本身父加載器。它們的實現不是經過繼承來實現的,而是經過組合的方式。當加載某個Class時,當前類加載器會把這個加載請求委派給其父類加載器加載,同理父類加載器一樣委派其它的父類加載器加載,直到無其父類類加載器加載爲止,若是父類加載器加載失敗,纔會由其子類加載。springboot
二、使用雙親委派機制,有個明顯的特徵是:Java類隨着它的類加載器一塊兒具有了一種優先級的層次關係。好比rt.jar包中的java.lang.Object,因爲它所在位置是由啓動類加載器加載,因此Object類在程序的各類類加載器環境中都是同一個類。網絡
三、雙親委派模型以下:
框架
能夠看下ClassLoader 雙親委派模型的大體代碼框架以下:jvm
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 } if (c == null) { // 父類加載器沒有加載到,則本身加載 long t1 = System.nanoTime(); c = findClass(name); // 記錄該類加載的狀態Stat. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // resolve :true,須要對類進行連接(連接階段包括:準備,解析,初始化類) if (resolve) { resolveClass(c); } return c; } }
一、經過以上能夠知道,咱們能夠繼承ClassLoader 來實現本身的類加載器,而後重寫findClass()方法,這些仍是保存了雙親委派機制。
二、當咱們重寫findClass()方法時,獲得該類的字節碼後,須要調用defineClass()來放回Class<?>對象。maven
一、通常自定義的類加載器能夠直接繼承該類,該類加載器經過添加url路徑來獲取類。
二、能夠在其構造函數上URLClassLoader(URL[] urls, ClassLoader parent)直接進行添加其URL。
三、也能夠經過addURL(URL)添加其URL。
一、首先假設main-project爲咱們本身編寫的工程,其依賴某一api:service-spi。而service-spi的實現有不少,project-spi-impl是其中的一個。
二、當main-project僅依賴service-spi,而project-spi-impl不在工程的類加載路徑下。因此須要自定義類加載器,從某個路徑下的jar加載進來。CoreClassLoader就是自定義的類加載器。
三、CoreClassLoader繼承自URLClassLoader,而後把相關搜索路徑添加到該類加載器便可。
4.github:https://github.com/zhvqee/class-loader
一、SpringBoot 工程經過spring-boot-maven-plugin插件打包。把相關資源和依賴包都打到一個jar包中(all in one)。其包的結構以下:
BOOT-INF/classes:存放的是本工程的class文件
BOOT-INF/lib:存放的是本工程依賴的二方包和三方包。
META-INF/MANIFEST.MF:該文件記錄了程序啓動入口等。
o.s.b.loader包下就是springboot自定義的類加載器。
二、當咱們執行java -jar xxxx 時,會讀取MANIFEST.MF下
Main-Class: org.springframework.boot.loader.JarLauncher,
該配置就是程序的路口。而咱們編寫的Main方法不是真正的啓動的入口。
三、當執行Launcher#launch()方法時,會把SpringBoot自定義的類加載器LaunchedURLClassLoader設置線程的上下文中,並經過該自定義類加載器加載咱們本身編寫的main方法所在的類,而後利用反射調用main方法。代碼片斷以下:
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.invoke(null, new Object[] { this.args });
四、LaunchedURLClassLoader 類加載器繼承URLClassLoader類加載器,它加載的路徑就是可執行jar下BOOT-INF下的class文件和二方包、三方包。
一、JVM 加載的類主要通過如下幾個步驟:加載,連接,初始化,試用,卸載。 二、Class.forName()默認是須要對加載的類進行初始化。 三、ClassLoader.load實際調用的是ClassLoader.load(className,false),false:表示不進行連接,不進行連接也就表明不會進行初始化的操做,類的靜態塊和靜態對象都不會執行。 該文章