Java類加載機制

Java類加載機制

Java類加載的生命週期

Java類加載的生命週期共分爲7個階段,分別是:加載、驗證、準備、解析、初始化、使用、卸載。其中驗證、準備、解析三部分稱爲鏈接。以下圖:java

image.png

  • 加載
    經過全限定名獲取類的二進制字節流並將其靜態存儲結構轉化爲方法區的運行時數據結構,且在內存中生成類的Class對象。
  • 驗證
    保證Class文件的字節流符合規範,不會危害虛擬機環境。主要有四種驗證:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
  • 準備
    將類中的定義的靜態變量分配內存並設置類變量初始值。
    eg:public static int value = 111;中在準備階段value設置的值爲0。
    注意: 在對靜態變量設置值時,常量例外,在準備階段直接對常量賦值爲程序員主觀定義的值
    eg:public static final int value = 122;,那麼在準備階段value常量設置的值爲122。
  • 解析
    將常量池中的符號引用替換爲直接引用。
  • 初始化
    根據程序員程序編碼制定的主觀計劃處初始化類變量和其餘資源。

類加載器

JDK9以前,Java應用主要由三種類加載器相互配合來完成加載的。程序員

  1. 啓用類加載器——Bootstrap ClassLoader
    該類加載器主要是使用C/C++實現,並不在Java類庫中。
    該加載器主要加載<JAVA_HOME>\Lib目錄下或被-Xbootclasspath參數制定路徑中存放的而且Java虛擬機可以識別的類庫。安全

    Java虛擬機安裝名字識別,如rt.jar、tools.jar, 其中名字不符合放在lib文件下也不會被加載
  2. 擴展類加載器——Extension ClassLoader
    該類是在sun.misc.Launcher$ExtClassLoader中以Java代碼的形式實現。
    主要負責加載<JAVA_HOME>\lib\ext目錄中或被java.ext.dirs系統變量制定路徑中的全部類庫。
  3. 應用程序類加載器——Application ClassLoader
    該類是在sun.misc.Launcher$AppClassLoader中以Java代碼的形式實現。
    主要負責加載用戶類路徑(ClassPath)上的全部類庫。數據結構

    雙親委派模型

    上述三種類加載器的關係以下圖:
    image.pngide

要求

雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應有本身的父類加載器。不過這裏類加載器的父子關係並不是類的繼承關係,而是加載器之間的關係。模塊化

工做過程

若是一個類加載器收到了類加載的請求,它不會首先本身嘗試加載這個類,而是把請求委託給父加載器來完成,每一層的類加載器都是如此,所以全部的加載請求最終都應傳到最頂層的啓動類加載器中。只有當父加載器沒法加載請求類時(在加載器搜索範圍內未找到類)。子加載器纔會嘗試本身完成加載。this

源碼解析:編碼

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 首先,檢查本類加載器是否已經加載該類
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 委託父類加載
                    c = parent.loadClass(name, false);
                } else {
                    // 啓動類加載器進行加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // 經過上面委託父類都沒有加載到,就本加載器在範圍內搜索類並加載
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

優勢

  • 安全機制: 本身寫的與核心API庫相同的類不會被加載。
  • 避免類的重複加載:當父類加載後,子類便不會加載,保證了被加載類的惟一性。

實現自定義符合雙親委派機制的類加載器

public class MyClassLoader extends ClassLoader {

    private String path;

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

    /**
     * 讀取字節流
     * @param name
     * @return
     * @throws IOException
     */
    private byte[] getClass(String name) throws IOException {
        String filePath = name.replaceAll("\\.", "/");
        FileInputStream fileInputStream = new FileInputStream(path + "/" + filePath + ".class");
        int len = fileInputStream.available();
        byte[] data = new byte[len];
        fileInputStream.read(data);
        fileInputStream.close();
        return data;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] aClass = new byte[0];
        try {
            aClass = getClass(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name, aClass, 0, aClass.length);
    }
}

打破雙親委派機制

Why

  • 系統基礎類型調用用戶的代碼(eg: JNDI)
  • 程序的動態性(eg:Hot Deployment)

實現打破雙親委派機制的自定義類加載器

public class MyClassLoader extends ClassLoader {

    private String path;

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

    /**
     * 讀取字節流
     * @param name
     * @return
     * @throws IOException
     */
    private byte[] getClass(String name) throws IOException {
        String filePath = name.replaceAll("\\.", "/");
        FileInputStream fileInputStream = new FileInputStream(path + "/" + filePath + ".class");
        int len = fileInputStream.available();
        byte[] data = new byte[len];
        fileInputStream.read(data);
        fileInputStream.close();
        return data;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    if (!name.startsWith("classload")) {
                        c = this.getParent().loadClass(name);
                    } else {
                        c = findClass(name);
                    }


                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] aClass = new byte[0];
        try {
            aClass = getClass(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name, aClass, 0, aClass.length);
    }
}

模塊化下的類加載器

JDK9以後類的加載機制發生了一些變化。spa

  • 啓動類加載器——Bootstrap ClassLoader在java中以BootClassLoader類實現
  • 本來的ExtClassLoader擴展類加載器在JDK9以後被PlatformClassLoaders平臺類加載器取代

JDK9以後的類加載委派關係

JDK9以後類加載委派關係的工做流程:
當平臺及應用程序類加載器收到類加載請求後,在委派給父類以前,要先判斷該類是否可以歸屬於系統的一個模塊中,若是有這樣的歸屬關係,則優先委派給相關模塊的加載器進行加載。code

其委派關係以下圖:
image.png

相關文章
相關標籤/搜索