相信每一位java開發工程師或多或少的對類載入有所了解,程序中通過java編譯器將一個.java文件編譯為.class文件,.class文件中包含著java代碼轉換後的虛擬機指令,當需要某個類時,虛擬機載入它的.class文件,並創建相對應的對象,將.class文件載入到jvm虛擬機內存,這個過程就稱之為類載入過程;

在上述過程中,一共可劃分為兩個步驟,別分是:編譯、運行;

編譯:把我們編寫好的java文件通過javac命令編譯成位元組碼,也就是我們通常說的.class文件。

運行:將編譯好的.class文件交給java虛擬機(jvm)執行生成對應的class對象裝載到內存的過程;

如果用一張圖來描述類的載入過程

1、 載入:在此階段,編譯.java文件,生成二進位位元組流.class文件,將其靜態存儲結構轉換成方法區的運行時數據結構,在堆內存中生成一個java.lang.Class對象,作為訪問方法區中數據的入口;

2、 驗證:驗證過程主要通過四個階段來確保載入的類的正確性;分別是文件格式的驗證(包含版本信息等)、位元組碼驗證、元數據驗證、符號引用驗證;

3、 準備:為類的靜態變數分類內存,並初始化默認值這一過程;

4、 解析:比如:將類中的符號引用轉換成直接引用;

5、 初始化:為類的靜態變數賦予正確的初始值,jvm負責對類進行初始化,主要對類變數進行初始化。

Jvm在啟動時,並不會一次性載入所有的class文件,而是根據需要去動態載入;

類的載入器

在java系統中的類載入器有三種;

1、 AppClassLoader :主要是載入當前應用classPath目錄下所有類;

2、 ExtClassLoader :擴展的類載入器,載入目錄%JRE_HOME%libext目錄下的jar包和class文件。還可以載入-D java.ext.dirs選項指定的目錄;

3、 BoostrapClassLoader:主要是載入核心類庫,如載入java.lang.System,主要載入%JRE_HOME%lib下的rt.jar、resources.jar、charsets.jar和class等

Bootstrp loader載入完ExtClassLoader後,就會載入AppClassLoader,並且將AppClassLoader的父載入器指定為 ExtClassLoader。

AppClassLoader也是用Java寫成的,它的實現類是 sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責載入classpath所指定的位置的類或者是jar文檔,它也是Java程序默認的類載入器。

某個特定的類載入器在接到載入類的請求時,首先將載入任務委託交給父類載入器,父類載入器又將載入任務向上委託,直到最父類載入器,如果最父類載入器可以完成類載入任務,就成功返回,如果不行就向下傳遞委託任務,由其子類載入器進行載入。

這個就是雙親委派機制的具體體現形式,其優點是保證了java核心庫的安全性(例如:如果用戶自己寫了一個java.lang.Long類就會因為雙親委派機制不能被載入,不會破壞原生的Long類的載入);

那麼,如果我們自定義一個java.lang.Long類,由於類載入器的雙親委派機制,這個自定義的類是不會被載入器載入的,因為載入器會載入系統核心類庫中的System類;

在上面我們編寫了一個簡單的類,不過這個類的包路徑是java.lang;然後當運行程序中的main方法時,直接拋出異常禁止使用包名:java.lang;

根據異常信息我們可以看到

java.lang.SecurityException: Prohibited package name: java.lang

at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)

在程序ClassLoader.java:662行被拋出異常,跟進去看一下:

根據源碼中的代碼示意

if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf(.)));
}

如果全限制名為null或者是全限制名是以java.開始,則拋出異常;也就是說,我們自己編寫代碼是不能定義包路徑為java開頭的;也就是說當如果類的全路徑名以java.開頭時,就會報錯;

自定義類載入器

通過上面的學習,我們知道了類的三種默認載入器以及分別應用場景以及優先順序關係;我們看一下自定義類載入器;

我們自定義類載入器必須繼承ClassLoader類重寫findClass方法,我們可以看一下ClassLoader類中loadClass方法都做了哪些處理;

在上面程序的424行,可以看到c = findClass(name); 可以看到在這個方法中並沒有做任何邏輯處理,而是直接拋出一個異常,並且,這個方式是被protected修飾的;

所以,我們自定義類載入器,直接重寫這個findClass方法即可;

在這裡,自定義類載入器,如果我們想繼續遵循雙親委派模型,我們直接重寫findClass方法即可;

如果需要打破雙親委派模型,我們就需要重寫整個loadClass方法,細心一點會發現,loadClass方法也是被protected修飾的;

所以在自定義類載入器時,可以根據實際應用場景來確定是否需要遵循系統中原有的雙親委派模型;

我們順便再看一下源碼的650行preDefineClass方法;

私有的preDefineClass方法中限定了程序包名的命名規則;那麼,當破壞了雙親委派模型後是否還能載入自定義的java.開頭的包名類呢?

2019年7月9日 17:15:56


推薦閱讀:
相关文章