Java類加載機制(二)

類加載器原理

將class文件字節碼內容加載到內存中,並將這些靜態數據轉換爲方法區的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class對象,做爲方法區類數據訪問的入口

類緩存 標準的JavaSE類記載器能夠按照要求查找類,但一旦某個類被加載到類加載器中, 它將維持加載(緩存)一段時間。不過,JVM垃圾回收器能夠回收這些Claas對象。java

類加載器樹狀結構、雙親委託機制

類加載器樹狀結構

  • 引導類加載器    用來加載Java的核心庫(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路徑下的內容),是用原生的代碼(c++)實現的,並不繼承java.lang.ClassLoader。加載擴展類加載器和應用程序類加載器。並指定它們的父類加載器。mysql

  • 擴展類記載器     用來加載Java的擴展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。 Java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載Java類。c++

  • 應用程序類加載器    它根據Java應用的類路徑(classpath,java.class.path路類)    通常來講,Java應用的類都是由它來完成加載的。由sun.misc.Launcher$AppClassLoader實現。web

  • 自定義類加載器    開發人員能夠經過繼承java.lang.ClassLoader類的方式實現本身的類加載器,以知足一些特殊的須要。sql

類加載器圖示

public class Demo {
    public static void main(String[] args) {
        //獲取應用程序類加載器
        System.out.println(ClassLoader.getSystemClassLoader());
        //獲取擴展類加載器
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        //獲取引導類加載器
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        //獲取classpath()
        //System.out.println(System.getProperty("java.class.path"));
    }
}
//結果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$ExtClassLoader@511d50c0
null
public class FileSystemClassLoader extends ClassLoader {
    String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = findLoadedClass(name);
        if (c != null) {
            return c;
        } else {
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(name);
            }catch (Exception e){
                e.printStackTrace();
            }
            if (c != null) {
                return c;
            } else {
                byte[] classData = getClassData(name);
                if (classData == null) {
                    throw new ClassNotFoundException("自定義類加載器沒有加載到");
                } else {
                    c = defineClass(name, classData, 0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String className) {

        String path = rootDir + "/" + className.replace(".", "/") + ".class";
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            byte[] buffer = new byte[1024];
            int temp = 0;
            while ((temp = is.read(buffer)) != -1) {
                baos.write(buffer,0,temp);
            }

            return baos.toByteArray();
        } catch (Exception e) {
            return null;
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(baos != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}


public class TestMyClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        FileSystemClassLoader loader = new FileSystemClassLoader("/Users/wjk/Desktop");
        FileSystemClassLoader loader1 = new FileSystemClassLoader("/Users/wjk/Desktop");
        Class c = loader.loadClass("com.Hello");
        Class c1 = loader1.loadClass("com.Hello");
        Class c2 = loader.loadClass("com.Hello");
        Class c3 = loader.loadClass("java.lang.String");


        System.out.println(c.hashCode());//被兩個類加載器加載的同一個類,JVM認爲是不一樣的(c和c1的hashCode值不同)
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());


        System.out.println(c.getClassLoader());//使用的是自定義的類加載器
        System.out.println(c3.getClassLoader());//使用的是引導類加載器
    }
}

//結果
1725154839
1670675563
1725154839
classLoaderTest.FileSystemClassLoader@5e2de80c
null

雙親委託機制

  • 代理模式:交給其餘類加載器加載指定的類緩存

  • 雙親委託機制    tomcat

    (1)就是當某個特定的類加載器接到加載類的請求的時候,首先委託給其父類(父類若是有父類一直向上追溯),直到父類加載器沒法加載時,該加載器進行加載。安全

    (2)雙親委託機制是爲了保證Java核心庫的類型安全。服務器

            這種機制保證不會加載到用戶自定義的java.lang.Class類的狀況 (3)類加載器除了用於加載類,也是安全最基本的屏障。數據結構

雙親委託機制是代理模式的一種,可是並非全部的類加載都是雙親委託機制,好比tomcat類加載器首先嚐試特定的 類加載器,加載不到類時在嘗試器父類加載器。

自定義類加載器

如何實現自定義類加載器:
(1)繼承java.lang.ClassLoader
(2)檢查所請求的類型是否已經被這個類加載器加載到命名空間,若是已經被加載直接返回。
(3)委派給父類加載(也能夠不委派,這個程序控制)。
(4)調用自定義加載器findClass()方法獲取字節碼,而後調用defineClass()導入類型到方法區。

線程上下文類加載器

雙親委派機制以及類加載器的問題

(1)通常狀況下,保證一個類中所關聯的其餘類都是由當前類加載器所加載的。
例如:ClassA自己在擴展類加載器下找到,那麼它裏面new出來的一些也就只能用擴展類加載器查找(
不會低一個級別),因此有的應用程序類加載器能夠找到,卻沒有找到。
(2)JDBC API。它有實現driven的部分(mysql/sql server),咱們的JDBC API都是有引導類加載器
或者擴展類加載器載入的,可是JDBC drive倒是由擴展類記載器或者應用程序類加載器載入,那麼就有可能找不到drive中,在Java領域,其實只要分紅Api+SPI(service provice interface特定廠商提供)的,都會遇到這個問題。簡而言之:接口定義在Java自己,實現卻在第三方,Java本自己使用引導類加載器或者擴展類加載器載入,而第三方實現使用擴展類加載器或者應用程序類記載器加載。

SPI接口是Java核心庫的一部分,是由引導類架子器加載的;SPI實現的Java類通常是由應用程序類加載器加載的。引導類加載器是沒法找到SPI的實現類的,由於它只加載Java的核心庫。

動態加載資源的時候必須須要的加載器

(1)系統類加載器
(2)當前類加載器
(3)當前線程類加載器

線程類加載器

線程類加載器是爲了拋棄雙親委託加載鏈式模式。
每個線程都有一個關聯上下文類加載器,若是用new Thread()方式生成
新的線程,新線程將繼續繼承其父線程的上下文類加載器。若是程序對線程上下文
類加載器沒有任何變更的話,程序中全部的線程將都使用系統類加載器(即:應用程序類
加載器)做爲上下文類加載器。
public class Demo3 {
    public static void main(String[] args) {
        //得到Demo類的類加載器
        ClassLoader loader1 = Demo.class.getClassLoader();
        System.out.println(loader1);
        //得到當前線程類加載器
        ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
        System.out.println(loader2);

        //使用自定義類加載器
        Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("/Users/wjk/Desktop"));
        //得到當前線程類加載器
        System.out.println(Thread.currentThread().getContextClassLoader());
    }
}
//結果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$AppClassLoader@2503dbd3
classLoaderTest.FileSystemClassLoader@60e53b93

服務器類加載器原理和OSGI介紹

服務器類加載器

(1)一切爲了安全,TOMCAT不能使用系統默認的類加載器。
緣由:
    若是TOMCAT跑你的web項目的時候使用系統默認的類加載器,

    你能夠直接肆無忌憚的操做系統的各個目錄,這是至關危險的。對於Java EE容器中的Web應用來講,類加載器的實現
與通常Java應用有所不一樣。每個Web應用都一個對應的類加載器,它先嚐試加載某個類,加載不了再委託給父類加載器,

(2)爲了安全TOMCAT須要實現本身的類加載器
    我能夠限制你把類寫在指定的位置,不然我不給你加載。

OSGI介紹

  • OSGI(Open Service Gateway Initative)是面向Java的動態模塊系統。它爲開發人員提供了 面向服務和基礎組件的運行環境,並提供標準的方式用來管理軟件的生命週期。

  • Eclipse就是基於OSGI技術構建的。

原理: OSGI中的每一個模塊都包含Java包和類。模塊能夠聲明它所依賴的須要導入的其餘模塊和Java包和 類,也能夠聲明本身導出的包和類,供其餘模塊來使用。也就是說可以隱藏和共享某些Java包和類。 這個是經過OSGI特有的類加載器機制來實現的。OSIG中每一個模塊都有對應一個類加載器,它負責加載模塊 本身包含的Java包和類。當它須要加載Java核心庫(java開頭的包和類)的類時,它要代理給父類加載器來完成。當它須要加載導入的Java類的時候,它會代理給導出此Java類的模塊來完成加載,模塊也能夠顯式聲明某些類和包必須由父類加載器加載。

相關文章
相關標籤/搜索