1. 程式人生 > >java 類的加載機制

java 類的加載機制

sync 什麽 false zed 範圍 引用 getc 偏移量 沒有

類加載器

類的加載是由類加載器完成的,類加載器包括:啟動類加載器(BootStrap)、擴展類加載器(ExtClassLoader)、應用程序類加載器(AppClassLoader)和自定義類加載器(java.lang.ClassLoader的子類)。
啟動類加載器

一般用本地代碼實現,負責加載JVM基礎核心類庫,即 JAVA_HOME\lib 目錄下的類。
擴展類加載器

繼承自啟動類加載器,加載 \lib\ext 下的類,或者被 java.ext.dirs 系統變量指定的類。
應用程序類加載器

繼承自擴展類加載器,加載 ClassPath 中的類,或者系統變量 java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

自定義類加載器

繼承自 ClassLoader 類。

為什麽要自定義類加載器

一方面是由於java代碼很容易被反編譯,如果需要對自己的代碼加密的話,可以對編譯後的代碼進行加密,然後再通過實現自己的自定義類加載器進行解密,最後再加載。

另一方面也有可能從非標準的來源加載代碼,比如從網絡來源,那就需要自己實現一個類加載器,從指定源進行加載。

類加載機制
全盤負責

當一個類加載器負責加載某個 Class 時,該 Class 所依賴的和引用的其他 Class 也將由該類加載器負責載入,除非顯式指定另外一個類加載器來載入。
雙親委派模型

如果一個類加載器收到了 Class 加載的請求,它首先不會自己去嘗試加載這個 Class ,而是把請求委托給父加載器去完成,依次向上。因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的 Class 時,即無法完成該加載,子加載器才會嘗試自己去加載該 Class 。

技術分享圖片


這樣做的好處是:
1. 避免同一個類被多次加載
2. 安全,Java 核心 API 中定義的類不會被隨意替換
3. 每個加載器只能加載自己範圍內的類
緩存機制

所有加載過的 Class 都會被緩存,當程序中需要使用某個 Class 時,類加載器先從緩存區尋找該 Class ,只有當緩存區不存在時,系統才會去讀取該 Class 對應的二進制數據,並將其轉換成 Class 對象,存入緩存區。

這就是為什麽修改了 Class 後,必須重啟JVM,程序的修改才會生效。
類加載器中的四個重要方法
loadClass(String name, boolean resolve)

protected Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先從緩存查找該class對象,找到就不用重新加載
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) {
// 如果都沒有找到,則通過自定義實現的findClass去查找並加載
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;
}
}


流程:
緩存 -> 父類加載器 -> 沒有父類 -> 啟動類加載器 -> 自己的 findClass() 方法
findClass(String name)

由自己負責加載類的方法。

在自定義類加載器時,需要重寫該方法並編寫加載規則,取得要加載類的字節碼後轉換成流,然後調用defineClass()方法生成類的 Class 對象。
defineClass(byte[] b, int off, int len)

將 byte 字節流解析成 JVM 能夠識別的 Class 對象。
resolveClass(Class??? c)

解析 Class 對象,即將字節碼文件中的符號引用轉換為直接引用。

符號引用與直接引用

符號引用:即一個字符串,但是這個字符串給出了一些能夠唯一性識別一個方法,一個變量,一個類的相關信息。

直接引用:可以理解為一個內存地址,或者一個偏移量。

舉個例子,現在調用方法 hello(),這個方法的地址是 1234567 ,那麽 hello 就是符號引用,1234567 就是直接引用。

類加載過程

類加載分為三個步驟:加載,連接,初始化

技術分享圖片


加載

根據一個類的全限定名(如 java.lang.String )來讀取該類的二進制字節流,解析成 JVM 能夠識別的 Class 對象。
連接
驗證

確保 Class 文件的字節流中包含信息符合虛擬機要求,不會危害虛擬機的安全。

主要包括四種驗證:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
準備

為類的靜態變量分配內存並且設置初始值,這裏的初始值指的是不同類型的默認值,如 int 默認值為0,引用的默認值為 null。

而 final 修飾的靜態常量,因為 final 在編譯的時候就會分配了,所以此時的值為代碼中設置的值。

註意

類的靜態變量會分配在方法區中,而實例變量是隨著對象一起分配到 Java 堆中。

解析

將常量池內的符號引用替換為直接引用。
初始化

將靜態變量和靜態方法塊按順序從上到下初始化,即為準備階段的靜態變量重新賦值,設置為代碼中指定的值。

執行構造函數。

如果該類具有父類,先初始化父類。
流程圖

技術分享圖片


子類繼承父類時的執行順序

技術分享圖片



---------------------
作者:路比船長
來源:CSDN
原文:https://blog.csdn.net/u013534071/article/details/80254247
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

java 類的加載機制