面試必備jvm類加載器

什麼是類的加載

我們平時所編寫的“xx.java”文件需要經過我們所知的java編譯器(javac)編譯成“xx.class”文件,這個文件存放着編譯後jvm指令的的二進制信息。而**當我們需要用到某個類時,jvm將會加載它,並在內存中創建對應的class對象,這個過程稱之爲類的加載。**過程如下:

類的加載、連接、初始化

1. 加載
通過類的包名和雷鳴查找到此類的字節碼文件,將xx.class文件中的二進制數據讀入到jvm內存,並存入其中的方法區內,然後利用字節碼文件創建一個class對象存入到堆之中,用來封裝類的數據結構等。
連接(驗證、準備、解析):

2. 連接包括三步:
驗證:確保被加載類的正確性,是否對jvm有害
準備:爲**類的靜態變量(static)**分配內存並將其賦值爲默認變量(非賦值)eg:static int a = 1;(爲a分配內存並設置爲0,將a賦值爲1的動作是在初始化過程中完成的,而final static int a = 1賦值是在javac過程中完成的。)
解析:把類中的符號引用轉化爲直接引用,符號引用:我們所寫的int a 就是符號引用,直接引用:目標的指針、偏移量內存中的引用,類似c/c++中的指針。

3. 初始化
如果有父類,此時加載父類並進行初始化,靜態變量、成員變量等,再加載子類。

類加載器

類的加載是由類加載器完成的。類加載器分爲jvm自帶的類加載器和用戶自定義的類加載器。
jvm內置加載器():
啓動類加載器Bootstrap ClassLoader
擴展類加載器Extention ClassLoader
系統類加載器System(Application) ClassLoader(應用類加載器)
用戶自定義加載器:
java.lang.ClassLoader的子類實例。

Bootstrap ClassLoader

最底層的類加載器,是jvm的一部分,由c++實現,沒有父加載器
主要負責加載由系統屬性sun.boot.class.path指定的路徑下的核心類庫(jre/lib),出於安全考慮該類加載器只加載只加載java,javax,sun相關包裏的類。

Extention ClassLoader

sun公司實現的sun.misc.Launcher$ExtClassLoader類(jdk8),由java編寫,負責加載jre/lib/ext目錄下的類庫或者由系統變量“java.ext.dirs”指定的目錄下的類庫。父加載器是Bootstrap ClassLoader(非繼承)。

System(Application) ClassLoader

一個純java類,sun公司實現的sun.misc.Launcher$AppClassLoader類(jdk8),父加載器是擴展加載器(Extention ClassLoader)(非繼承),負責從classpath環境變量或者java.class.path所指定的目錄中加載類。是用戶自定義的類加載器的默認父加載器。一般情況下是程序默認類加載器,可通過*ClassLoader.getSystemClassLoader()*直接獲得。

雙親委派模型

在這裏插入圖片描述
雙親委派機制
當需要使用該類時,纔會將它的class文件加載到內存生成class對象(按需加載),加載時,使用的是雙親委派模式:如果一個類加載器收到了類請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父加載器去完成,每一層都是如此,因此所有類加載的請求都會傳到啓動類加載器,只有當父加載器無法完成該請求時,子加載器纔去自己加載。(父加載器、子加載器:非繼承關係,而是用組合模式來複用父加載器代碼)

雙親委派機制的優點:

  1. 避免類的重複加載,當父加載器加載成功時,就沒必要子加載器再去加載
  2. 安全。防止java核心API不會被隨意替換。比如一個網絡上的java.lang.Object類,無論哪個類加載器去加載該類,最終都是由啓動類加載器進行加載的,因此Object類在程序的各種類加載環境中都是一個類。如果不委託,那麼java.lang.Object類存放在classpath中,那麼系統中就會出現多個Object類,程序變得很混亂。

ClassLoader

除了啓動類加載器Bootstrap ClassLoader,其它類加載器都必須繼承java.lang.ClassLoader(abstract)
主要方法:
loadClass();!!!不要覆寫此方法
findClass();
defineClass();
resolveClass();
相關使用參考此處

URLClassLoader

java.net.URLClassLoader,擴展了ClassLoader。可以從本地、網絡上的指定url加載類。可使用該類作爲自定義的類加載器使用。

  1. 加載磁盤上的class文件:

現在有一class文件 F:/Test01.class在這裏插入圖片描述
Test01中有默認構造方法:

public Test01(){
  System.out.println("test01");
 }

此時我們利用URLClassLoader就可以加載磁盤上的class文件:

File file = new File("F:/");
URI uri = file.toURI();
URL url = uri.toURL();

URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
System.out.println(classLoader.getParent()); //父加載器
Class aClass = classLoader.loadClass("Test01");
aClass.newInstance();//驗證是否調用默認構造方法

對應輸出:
在這裏插入圖片描述
可以看到默認構造方法已經被調用。

  1. 加載網絡上的class文件
    類似,請讀者自行測試

自定義類加載器

方法:繼承ClassLoader,並覆寫findClass()方法。

自定義文件類加載器

這裏同上面一樣,存在 F:/Test01.class
我們自定義一個文件類加載器來實現這個類的加載:

import java.io.*;

/*
 * 自定義本地類加載器
 * director 類所在的目錄
 */
public class MyFileClassLoader extends ClassLoader{
    private String director;

    //默認父加載器爲應用類加載器(ApplicationClassLoader)
    public MyFileClassLoader(String director) {
        this.director = director;
    }

    //指定父加載器
    public MyFileClassLoader(String director, ClassLoader parentClassLoader) {
        super(parentClassLoader);
        this.director = director;
    }

    /**
     * @param name 類名
     * @return 返回字節碼對象
     */
    @Override
    protected Class<?> findClass(String name) {
        try {
            //將類轉化爲真正對應class文件的目錄
            String file = director + File.separator + name.replace(".", "/") + ".class";

            //構建輸入輸出流
            InputStream is = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

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

            //讀取到的字節碼的二進制數據
            byte[] data = baos.toByteArray();
            is.close();
            baos.close();

            /*
             * name - 預期的 binary name的類
             * data - 構成類數據的字節。
             * 0 - 類數據中的起始偏移量爲 0
             * data.length - 類數據的長度
             */
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
           throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
        Class aClass = myClassLoader.loadClass("Test01");
        aClass.newInstance();
    }
}

網絡類加載器

網絡類加載器類似(相關流API的使用)

熱部署類加載器

**避開雙親委派模式,實現一個類的不同類加載器的加載。**我們已經知道雙親委派模式主要是以loadClass()方法實現的,那麼我們可以不使用loadClass()而是用findClass()方法,避開雙親委派,實現一個類的多次加載,達到熱部署。

public static void main(String[] args) throws Exception {
    MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
    MyFileClassLoader myClassLoader2 = new MyFileClassLoader("F:/", myClassLoader);

    Class aClass = myClassLoader.findClass("Test01");
    Class aClass2 = myClassLoader2.findClass("Test01");
    System.out.println(aClass.hashCode());
    System.out.println(aClass2.hashCode());
}

輸出結果:
455896770
1323165413
可以看到哈希碼不同,證明一個類被兩個加載器都加載了。

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