java类加载器_java底层内功 第一章,类加载器的任性
java類是怎么加載的?
類加載機制
JVM主要包含三大核心部分:類加載器,運行時數據區和執行引擎。
虛擬機將描述類的數據從class文件加載到內存,并對數據進行校驗,準備,解析和初始化,最終就會形成可以被虛擬機使用的java類型,這就是一個虛擬機的類加載機制。java在類中的類是動態加載的,只有在運行期間使用到該類的時候,才會將該類加載到內存中,java依賴于運行期動態加載和動態鏈接來實現類的動態使用。
一個類的生命周期:
Paste_Image.png
加載,驗證,準備,初始化和卸載在開始的順序上是固定的,但是可以交叉進行。在Java中,對于類有且僅有四種情況會對類進行“初始化”。
- 使用new關鍵字實例化對象的時候,讀取或設置一個類的靜態字段時候(除final修飾的static外),調用類的靜態方法時候,都只會初始化該靜態字段或者靜態方法所定義的類。
- 使用reflect包對類進行反射調用的時候,如果類沒有進行初始化,則先要初始化該類。
- 當初始化一個類的時候,如果其父類沒有初始化過,則先要觸發其父類初始化。
- 虛擬機啟動的時候,會初始化一個有main方法的主類。
注意:
- 子類引用父類靜態字段,只會初始化父類不會初始化子類
- 通過數組定義來引用類,也不會觸發該類的初始化
- 常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,因此也不會觸發定義常量的類的初始化
類加載過程
加載
加載階段主要完成三件事,即通過一個類的全限定名來獲取定義此類的二進制字節流,將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構,在Java堆中生成一個代表此類的Class對象,作為訪問方法區這些數據的入口。這個加載過程主要就是靠類加載器實現的,這個過程可以由用戶自定義類的加載過程。
驗證
這個階段目的在于確保才class文件的字節流中包含信息符合當前虛擬機要求,不會危害虛擬機自身安全。
主要包括四種驗證:
- 文件格式驗證:基于字節流驗證,驗證字節流是否符合Class文件格式的規范,并且能被當前虛擬機處理。
- 元數據驗證:基于方法區的存儲結構驗證,對字節碼描述信息進行語義驗證。
- 字節碼驗證:基于方法區的存儲結構驗證,進行數據流和控制流的驗證。
- 符號引用驗證:基于方法區的存儲結構驗證,發生在解析中,是否可以將符號引用成功解析為直接引用。
準備
僅僅為類變量(即static修飾的字段變量)分配內存并且設置該類變量的初始值即零值,這里不包含用final修飾的static,因為final在編譯的時候就會分配了(編譯器的優化),同時這里也不會為實例變量分配初始化。類變量會分配在方法區中,而實例變量是會隨著對象一起分配到Java堆中。
解析
解析主要就是將常量池中的符號引用替換為直接引用的過程。符號引用就是一組符號來描述目標,可以是任何字面量,而直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。有類或接口的解析,字段解析,類方法解析,接口方法解析。
初始化
初始化階段依舊是初始化類變量和其他資源,這里將執行用戶的static字段和靜態語句塊的賦值操作。這個過程就是執行類構造器< clinit >方法的過程。
< clinit >方法是由編譯器收集類中所有類變量的賦值動作和靜態語句塊的語句生成的,類構造器< clinit >方法與實例構造器< init >方法不同,這里面不用顯示的調用父類的< clinit >方法,父類的< clinit >方法會自動先執行于子類的< clinit >方法。即父類定義的靜態語句塊和靜態字段都要優先子類的變量賦值操作。
類加載器
類加載器的分類
- 啟動類加載器(Bootstrap ClassLoader):主要負責加載lib目錄中的'.'或是-Xbootclasspath參數指定的路徑中的,并且可以被虛擬機識別(僅僅按照文件名識別的)的類庫到虛擬機內存中。它加載的是System.getProperty("sun.boot.class.path")所指定的路徑或jar。
- 擴展類加載器(Extension ClassLoader):主要負責加載libext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫。它加載的是
System.getProperty("java.ext.dirs")所指定的路徑或jar。 - 應用程序類加載器(Application ClassLoader):也叫系統類加載器,主要負責加載ClassPath路徑上的類庫,如果應用程序沒有自定義自己類加載器,則這個就是默認的類加載器。它加載的是System.getProperty("java.class.path")所指定的路徑或jar。
類加載器的特點
- 運行一個程序時,總是由Application Loader(系統類加載器)開始加載指定的類。
- 在加載類時,每個類加載器會將加載任務上交給其父,如果其父找不到,再由自己去加載。
- Bootstrap Loader(啟動類加載器)是最頂級的類加載器了,其父加載器為null。
類加載器的雙親委派模型
類加載器雙親委派模型的工作過程是:如果一個類加載器收到一個類加載的請求,它首先將這個請求委派給父類加載器去完成,每一個層次類加載器都是如此,則所有的類加載請求都會傳送到頂層的啟動類加載器,只有父加載器無法完成這個加載請求(即它的搜索范圍中沒有找到所要的類),子類才嘗試加載。
使用雙親委派模型主要是兩個原因:
- 可以避免重復加載,當父類已經加載了,則就子類不需再次加載;
- 安全因素,如果不用這種,則用戶可以隨意的自定義加載器來替代Java核心API,則就會帶來安全隱患。
下面是一個類加載器雙親委派模型,這里各個類加載器并不是繼承關系,它們利用組合實現的父類與子類關系。
雙親委托模型
類加載的幾種方式
- 命令行啟動應用時候由JVM初始化加載,加載含有main的主類。
- 通過Class.forName("Hello")方法動態加載類,默認會執行初始化塊,這是因為Class.forName("Hello")其實就是Class.forName("Hello",true,CALLCLASS.getClassLoader()),第二個參數就是類加載過程中的連接操作。如果指定了ClassLoader,則不會執行初始化塊。
- 通過ClassLoader.loadClass("Hello")方法動態加載類,不會執行初始化塊,因為loadClass方法有兩個參數,用戶只是用第一個參數,第二個參數默認為false,即不對該類進行解析則就不會初始化。
類加載實例
當在命令行下執行:java HelloWorld(HelloWorld是含有main方法的類的Class文件),JVM會將HelloWorld.class加載到內存中,并在堆中形成一個Class的對象HelloWorld.class。
基本的加載流程如下:
- 尋找jre目錄,尋找jvm.dll,并初始化JVM;
- 產生一個Bootstrap Loader(啟動類加載器);
- Bootstrap Loader,該加載器會加載它指定路徑下的Java核心API,并且再自動加載Extended Loader(標準擴展類加載器),Extended Loader會加載指定路徑下的擴展JavaAPI,并將其父Loader設為BootstrapLoader。
- Bootstrap Loader也會同時自動加載AppClass Loader(系統類加載器),并將其父Loader設為ExtendedLoader。
- 最后由AppClass Loader加載CLASSPATH目錄下定義的類,HelloWorld類。
創建自己的類加載器
在Java應用開發過程中,可能會需要創建應用自己的類加載器。典型的場景包括實現特定的Java字節代碼查找方式、對字節代碼進行加密/解密以及實現同名Java類的隔離等。創建自己的類加載器并不是一件復雜的事情,只需要繼承自java.lang.ClassLoader類并覆寫對應的方法即可。 java.lang.ClassLoader中提供的方法有不少,下面介紹幾個創建類加載器時需要考慮的:
- defineClass():這個方法用來完成從Java字節碼的字節數組到java.lang.Class的轉換。這個方法是不能被覆寫的,一般是用原生代碼來實現的。
- findLoadedClass():這個方法用來根據名稱查找已經加載過的Java類。一個類加載器不會重復加載同一名稱的類。
- findClass():這個方法用來根據名稱查找并加載Java類。
- loadClass():這個方法用來根據名稱加載Java類。
- resolveClass():這個方法用來鏈接一個Java類。
這里比較 容易混淆的是findClass()方法和loadClass()方法的作用。前面提到過,在Java類的鏈接過程中,會需要對Java類進行解析,而解析可能會導致當前Java類所引用的其它Java類被加載。在這個時候,JVM就是通過調用當前類的定義類加載器的loadClass()方法來加載其它類的。findClass()方法則是應用創建的類加載器的擴展點。應用自己的類加載器應該覆寫findClass()方法來添加自定義的類加載邏輯。 loadClass()方法的默認實現會負責調用findClass()方法。
前面提到,類加載器的代理模式默認使用的是父類優先的策略。這個策略的實現是封裝在loadClass()方法中的。如果希望修改此策略,就需要覆寫loadClass()方法。
下面的代碼給出了自定義的類加載的常見實現模式:
public class MyClassLoader extends ClassLoader { protected Class> findClass(String name) throws ClassNotFoundException { byte[] b = null; //查找或生成Java類的字節代碼 return defineClass(name, b, 0, b.length); }}破壞雙親委派模型的場景
總結
以上是生活随笔為你收集整理的java类加载器_java底层内功 第一章,类加载器的任性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019央行存款准备金率是多少?下调存款
- 下一篇: 光大绿色零碳信用卡申请条件及额度