目錄
詳解類加載機制
類的加載指將編譯好的 Class 類文件中的字節碼讀入內存中,將其放在方法區內並創建對應的 Class 對象。類的加載分爲加載、鏈接、初始化,其中鏈接又包括驗證、準備、解析三步。如下圖所示。
-
加載是文件到內存的過程。通過類的完全限定名查找此類字節碼文件,並利用字節碼文件創建一個 Class 對象。
-
驗證是對類文件內容驗證。目的在於確保 Class 文件符合當前虛擬機要求,不會危害虛擬機自身安全。主要包括四種:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
-
準備階段是進行內存分配。爲類變量也就是類中由 static 修飾的變量分配內存,並且設置初始值。這裏要注意,初始值是 0 或者 null,而不是代碼中設置的具體值,代碼中設置的值是在初始化階段完成的。另外這裏也不包含用 final 修飾的靜態變量,因爲 final 在編譯的時候就會分配。
-
解析主要是解析字段、接口、方法。主要是將常量池中的符號引用替換爲直接引用的過程。直接引用就是直接指向目標的指針、相對偏移量等。
-
初始化,主要完成靜態塊執行與靜態變量的賦值。這是類加載最後階段,若被加載類的父類沒有初始化,則先對父類進行初始化。
只有對類主動使用時,纔會進行初始化,初始化的觸發條件包括在創建類的實例時、訪問類的靜態方法或者靜態變量時、Class.forName() 反射類時、或者某個子類被初始化時。
類的生命週期就是從類的加載到類實例的創建與使用,再到類對象不再被使用時可以被 GC 卸載回收。這裏要注意一點,由 Java 虛擬機自帶的三種類加載器加載的類在虛擬機的整個生命週期中是不會被卸載的,只有用戶自定義的類加載器所加載的類纔可以被卸載。
詳解類加載器
如上圖所示,Java 自帶的三種類加載器分別是:BootStrap 啓動類加載器、擴展類加載器和應用加載器(也叫系統加載器)。圖右邊的文字表示各類加載器對應的加載目錄。啓動類加載器加載 JAVA_HOME 中 lib 目錄下的類,擴展加載器負責加載 ext 目錄下的類,應用加載器加載 classpath 指定目錄下的類。除此之外,可以自定義類加載器。
-
啓動類加載器(Bootstrap ClassLoader),在java裏無法獲取
負責將存放在<JAVA_HOME>/lib
目錄中的,或者被-Xbootclasspath
參數所指定的路徑中的,並且是虛擬機按照文件名識別的(如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。
啓動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可。
JDK中的常用類大都由啓動類加載器加載,如java.lang.String、java.util.List等。需要特別說明的是,啓動類Main class也由啓動類加載器加載。
-
擴展類加載器(Extension ClassLoader),可以在java裏獲取
由sun.misc.Launcher$ExtClassLoader
實現。負責加載<JAVA_HOME>/lib/ext
目錄中的,或者被java.ext.dirs
系統變量所指定的路徑中的所有類庫。
-
應用程序類加載器(Application ClassLoader)
由sun.misc.Launcher$AppClassLoader
實現。由於這個類加載器是ClassLoader.getSystemClassLoader()
方法的返回值,所以一般也稱它爲系統類加載器。
它負責加載用戶類路徑ClassPath
上所指定的類庫,開發者可以直接使用這個類加載器。如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
啓動類Main class、其他如工程中編寫的類、maven引用的類,都會被放置在類路徑下。但是需要注意:Main class由啓動類加載器加載,其他類由應用程序類加載器加載。
雙親委派
Java 的類加載使用雙親委派模式,如果一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給父類加載器,每一個層次的類加載器都是如此,因此所有的類加載請求最終都會傳送到頂端的啓動類加載器;只有當父類加載器在其搜索範圍內無法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去自己加載。
這裏有幾個流程要注意一下:
- 子類先委託父類加載
- 父類加載器有自己的加載範圍,範圍內沒有找到,則不加載,並返回給子類
- 子類在收到父類無法加載的時候,纔會自己去加載
這種雙親委派模式的好處,
1. 可以避免類的重複加載
2.避免了 Java 的核心 API 被篡改。核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名爲java.lang.Integer的類,通過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java
API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。