java八股文-類加載器----------面試筆記

類加載器:
加載器負責加載所有的類,其爲所有被載入內存中的類生成一個java.lang.Class實例對象。
一旦一個類被加載如JVM中,同一個類就不會被再次載入了。正如一個對象有一個唯一的標識一樣,一個載入JVM的類也有一個唯一的標識。
在Java中,一個類用其全限定類名(包括包名和類名)作爲標識;但在JVM中,一個類用其全限定類名和其類加載器作爲其唯一標識。
例如,如果在pg的包中有一個名爲Person的類,被類加載器ClassLoader的實例kl負責加載,則該Person類對應的Class對象在JVM中表示爲(Person.pg.kl)。
這意味着兩個類加載器加載的同名類:(Person.pg.kl)和(Person.pg.kl2)是不同的、它們所加載的類也是完全不同、互不兼容的

JVM預定義有三種類加載器,當一個 JVM啓動的時候,Java開始使用如下三種類加載器
1.根類加載器 2.擴展類加載器 3.系統類加載器
根類加載器(bootstrap class loader):它用來加載 Java 的核心類,是用原生代碼來實現的,
並不繼承自 java.lang.ClassLoader(負責加載$JAVA_HOME中jre/lib/rt.jar裏所有的class,由C++實現,不是ClassLoader子類)。
由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啓動類加載器的引用,所以不允許直接通過引用進行操作
負責將java安裝路徑下的lib目錄下的jar文件加載到內存中,如rt.jar,由於虛擬機加載是指定了文件名的,所以新建其他的包也沒有作用。
public class ClassLoaderTest {
 
    public static void main(String[] args) {
        
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for(URL url : urls){
            System.out.println(url.toExternalForm());
        }
    }
}
擴展類加載器
加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,由Java語言實現,父類加載器爲null。
系統類加載器(system class loader):被稱爲系統(也稱爲應用)類加載器,它負責在JVM啓動時加載來自Java命令的-classpath選項、java.class.path系統屬性,
或者CLASSPATH換將變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。
如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作爲父加載器。由Java語言實現,父類加載器爲ExtClassLoader。
開發者可以直接使用標準擴展類加載器。
//ExtClassLoader類中獲取路徑的代碼
private static File[] getExtDirs() {
     //加載<JAVA_HOME>/lib/ext目錄中的類庫
     String s = System.getProperty("java.ext.dirs");
     File[] dirs;
     if (s != null) {
         StringTokenizer st =
             new StringTokenizer(s, File.pathSeparator);
         int count = st.countTokens();
         dirs = new File[count];
         for (int i = 0; i < count; i++) {
             dirs[i] = new File(st.nextToken());
         }
     } else {
         dirs = new File[0];
     }
     return dirs;
 }
 
 
 系統類加載器:被稱爲系統(也稱爲應用)類加載器,它負責在JVM啓動時加載來自Java命令的-classpath選項、java.class.path系統屬性,
 或者CLASSPATH換將變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。
 如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作爲父加載器。由Java語言實現,父類加載器爲ExtClassLoader。
 
類加載器加載Class大致要經過如下8個步驟:
 檢測此Class是否載入過,即在緩衝區中是否有此Class,如果有直接進入第8步,否則進入第2步。
 如果沒有父類加載器,則要麼Parent是根類加載器,要麼本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步。
 請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接着執行第5步。
 請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步。
 當前類加載器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步。
 從文件中載入Class,成功後跳至第8步。
 拋出ClassNotFountException異常。
 返回對應的java.lang.Class對象。

雙親委派模式原理
雙親委派模式是在Java 1.2後引入的,其工作原理的是,如果一個類加載器收到了類加載請求,它並不會自己先去加載,
而是把這個請求委託給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,
請求最終將到達頂層的啓動類加載器,如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器纔會嘗試自己去加載 
雙親委派模式優勢
採用雙親委派模式的是好處是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,通過這種層級關可以避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次

 雙親委託模型的破壞
 由於雙清委託模型自身的缺陷導致,Java 提供了很多服務提供者接口(Service Provider Interface,SPI),允許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現代碼則是作爲 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)裏。
SPI接口中的代碼經常需要加載具體的實現類。那麼問題來了,SPI的接口是Java核心庫的一部分,是由啓動類加載器(Bootstrap Classloader)來加載的;
SPI的實現類是由系統類加載器(System ClassLoader)來加載的。引導類加載器是無法找到 SPI 的實現類的,因爲依照雙親委派模型,BootstrapClassloader無法委派AppClassLoader來加載類。
關於SPI(Service Provider Interface)請參考:https://www.jianshu.com/p/46b42f7f593c

上邊的問題往簡單裏說就是服務提供者接口屬於核心庫(Bootstrap Classloader加載),而接口實現類屬於實現類(System ClassLoader加載),啓動類加載器五法委派系統類加載器,自然而產產生解決方案:線程上下文類加載器(TCCL)

線程上下文類加載器(TCCL)
類是通過java.util.ServiceLoader類來加載,而ServiceLoader.class又加載在BootrapLoader中,此時肯定不能通過BootrapLoader來加載類,只能使用TCCL了,也就是說把自己加載不了的類加載到TCCL中
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();//當前線程獲取上下文加載器
    return ServiceLoader.load(service, cl);
}
1
2
3
4
ContextClassLoader默認存放了AppClassLoader的引用,由於它是在運行時被放在了線程中,所以不管當前程序處於何處(BootstrapClassLoader或是ExtClassLoader等),在任何需要的時候都可以Thread.currentThread().getContextClassLoader()取出應用程序類加載器來完成需要的操作

1、當高層提供了統一接口讓低層去實現,同時又要是在高層加載(或實例化)低層的類時,必須通過線程上下文類加載器來幫助高層的ClassLoader找到並加載該類。
2、當使用本類託管類加載,然而加載本類的ClassLoader未知時,爲了隔離不同的調用者,可以取調用者各自的線程上下文類加載器代爲託管。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章