Java中的類加載機制

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/isee361820238/article/details/52703086

一、Java的類加載簡介

先了解一個基本概念,類的初始化。

當程序使用某個類時,如果該類還沒被初始化,加載到內存中,則系統會通過加載、鏈接、初始化三個過程來對該類進行初始化,該過程稱爲類的初始化。

那麼類加載又是啥呢?說到類加載就必須要知道java.lang.ClassLoader類,它的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節代碼,然後從這些字節代碼中定義出一個Java 類,即 java.lang.Class類的一個實例。它有以下幾個重要的方法:

  • getParent():返回該類加載器的父類加載器;
  • loadClass(String className):加載名稱爲className的類,返回的結果是java.lang.Class類的實例;
  • findClass(String className):查找名稱爲className的類,返回的結果是java.lang.Class類的實例;
  • findLoadedClass(String className):查找名稱爲className的已經被加載過的類,返回的結果是java.lang.Class類的實例;
  • defineClass(String className, byte[] b, int off, int len):把字節數組b中的內容轉換成Java類,返回的結果是java.lang.Class的實例,這個方法被聲明爲final的;
  • resolveClass(Class

二、JVM中的類加載器

類加載器就是負責加載所有的類,並將其載入內存中,生成一個java.lang.Class的實例。一旦一個類被加載到JVM中之後,就不會被再次載入了。

類加載器通常無須等到“首次使用”該類時才加載該類,JVM允許系統預先加載某些類

Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。

  • 根類加載器(Bootstrap ClassLoader):其負責加載Java的核心類,比如String、System這些類,是用原生C++代碼來實現的,並不繼承自java.lang.ClassLoader;
  • 拓展類加載器(Extension ClassLoader):其負責加載JRE的拓展類庫(jre/ext/*.jar),Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載Java類;
  • 系統類加載器(System ClassLoader):其負責加載CLASSPATH環境變量所指定的JAR包和類路徑,一般來說,Java應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它;
  • 自定義類加載器(Custom ClassLoader):用戶自定義的加載器,以java.lang.ClassLoader類爲父類。

測試一下類加載器的層次關係:

public static void main(String[] args) {
    //application class loader
    System.out.println(ClassLoader.getSystemClassLoader());
    //extensions class loader
    System.out.println(ClassLoader.getSystemClassLoader().getParent());
    //bootstrap class loader
    System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}

打印結果如下:

sun.misc.Launcher$AppClassLoader@3e55a58f
sun.misc.Launcher$ExtClassLoader@68e86f41
null

可以看出ClassLoader類是由AppClassLoader加載的,它的父加載器是ExtClassLoader,而ExtClassLoader的父加載器無法獲取,是因爲它是用C++實現的。

注意,這裏類加載器之間的父子關係並不是繼承關係,而是類加載器實例之間的關係。

三、JVM中的類加載機制

  • 全盤負責機制,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入;
  • 雙親委託機制,首先將加載任務委託交給父類加載器,父類加載器又將加載任務向上委託,直到最父類加載器,如果最父類加載器可以完成類加載任務,就成功返回,如果不行就向下傳遞委託任務,由其子類加載器進行加載;
  • 代理機制:與雙親委派機制相反,代理模式是先自己嘗試加載,如果無法加載則向上傳遞,tomcat就是代理模式;
  • 緩存機制,緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲什麼修改了Class後,必須重啓JVM,程序的修改纔會生效。

四、自定義類加載器實例

public class MyClassLoader extends ClassLoader{

    private String rootPath;

    public MyClassLoader(String rootPath){
        this.rootPath = rootPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //check if the class have been loaded
        Class<?> c = findLoadedClass(name);        
        if(c!=null){
            return c;
        }
        //load the class
        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 = rootPath+"/"+className.replace('.', '/')+".class";

        InputStream is = null;
        ByteArrayOutputStream bos = null;
        try {
            is = new FileInputStream(path);
            bos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp = is.read(buffer))!=-1){
                bos.write(buffer,0,temp);
            }
            return bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                is.close();
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }            
        }

        return null;        
    }    

}

創建一個測試類HelloWorld:

package testOthers;

public class HelloWorld {

}

在D盤根目錄創建一個testOthers文件夾,編譯HelloWorld.java,將得到的class文件放到testOthers文件夾下。

利用如下代碼進行測試:

public class testMyClassLoader {
    @Test
    public void test() throws Exception{
        MyClassLoader loader = new MyClassLoader("D:");
        Class<?> c = loader.loadClass("testOthers.HelloWorld");
        System.out.println(c.getClassLoader());
    }
}

最終輸出:

test.ClassLoader.MyClassLoader@3e55a58f

說明HelloWorld類是被我們的自定義類加載器MyClassLoader加載的。

五、Java類加載器的過程

JVM將類加載的過程分爲三個步驟:裝載(Load),鏈接(Link)和初始化(Initialize)

image

1.裝載:

查找並加載類的二進制數據;

2.鏈接:

當類被加載後,系統會爲之生成一個Class對象,接着將會進入鏈接階段,鏈接階段負責把類的二進制數據合併到JRE中,它有三個階段:
- 驗證:確保被加載類信息符合JVM規範、沒有安全方面的問題。
- 準備:爲類的靜態變量分配內存,並將其初始化爲默認值。
- 解析:把虛擬機常量池中的符號引用轉換爲直接引用。

3.初始化:

當一個Java類第一次被真正使用到的時候,JVM會進行該類的初始化操作。初始化過程的主要操作是執行靜態代碼塊和初始化靜態域。在一個類被初始化之前,它的直接父類也需要被初始化。

Java類和接口的初始化只有在特定的時機纔會發生,這些時機包括:
- 創建一個Java類的實例。如

MyClass obj = new MyClass()
  • 調用一個Java類中的靜態方法。如
MyClass.sayHello()
  • 給Java類或接口中聲明的靜態域賦值。如
MyClass.value = 10
  • 訪問Java類或接口中聲明的靜態域,並且該域不是常值變量。如
int value = MyClass.value
  • 在頂層Java類中執行assert語句。
  • 使用反射方式強制創建某個類或接口對應的java.lang.Class對象。
  • 初始化某個類的子類,則其父類也會被初始化。

JVM初始化類的步驟
- 如果這個類還沒有被加載和鏈接,那先進行加載和鏈接;
- 假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)
- 如果類中存在static標識的塊,那就依次執行這些初始化語句。

Java 中,虛擬機會爲每個加載的類維護一個常量池【不同於字符串常量池,這個常量池只是該類的字面值(例如類名、方法名)和符號引用的有序集合。 而字符串常量池,是整個JVM共享的】這些符號(如int a = 5;中的a)就是符號引用,而解析過程就是把它轉換成指向堆中的對象地址的相對地址。

參考:http://www.cnblogs.com/sunniest/p/4574080.html

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