JVM类加载机制(ClassLoader)源码解析
http://blog.csdn.net/chenyi8888/article/details/7066569
其實JVM類加載機制,簡單地說就是類管理,也就是我們生成的class文件。
三個步驟:裝載(load)、鏈接(link)、解析(Resolve)、還有初始化(Initialize)
關于網上有很多講解加載的方式,和調用的方式,還是幾個基本的classLoader,這里就不在多描述了。
這里更多的是從源碼上來講解,達到理論結合實際。
首先是ClassLoader這個抽象類,這個是實現自定義類的基礎。那么在調用的時候,首先都是調用loadClass這個方法,如圖:
該方法調用的是一個重載方法loadClass(name,false)方法如圖:
第一行注釋很清楚的表示,從當前的類加載器里加載class文件,如果已經加載,就直接返回;不存在就加載;如果返回NULL,表示class文件不在這個類加載器的范圍內。
這是就會執行if(c==null)下面的方法,就會去找父加載器(類加載器的整個結構是樹形的,這里不多介紹),如果父加載器不存在,就直接找到根加載器(根加載器一定存在)。
我們現在開始解析每個被調用方法。
首先看下findLoadedClass方法,如圖:
該方法首先,校驗類名是否正確(類似于 test.demo.Test.class),校驗成功后,調用本地方法findLoadedClass0方法。
接著是parent.loadClass(name,false)方法,該方法是重復的,只是對象不同而已,就不介紹了。
接著是findBootstrapClassOrNull方法,如圖:
方式與findLoadedClass方法一樣,就是本地調用是findBootstrapClass方法。
如果此時類還不存在,有一個findClass方法的調用。代碼如圖:
這里看上去覺得很奇怪,直接就拋一個空異常了,其實根據注釋所說,就是自定義的類加載器需要實現這種方法。
為什么要實現這個方法呢?原因就是有些場景會需要通過網絡傳輸的方式進行加載類(該類其實是在遠程服務器上的),在很多地方就會用到,例如遠程調用等技術。
具體的實現例子,在URLClassLoader里就有體現,這里暫不介紹。
最后是調用了resolveClass方法,代碼如圖:
這個方法很簡單,主要是重新進行解析類,該解析主要是對所有的屬性/方法調用是否存在、相應的權限(如private、public等)進行驗證。那么為什么默認都是傳入false呢?
那是因為是否需要更強的安全機制的檢測,
另一個情況是類加載是無序的,會導致類鏈接不成功。
這里有個狀態位的控制,主要是可能有些場景需要更嚴格的對類進行驗證(目前我還沒有使用到這樣的場景)。
這里要注意的是:
類為什么是樹形結構,主要就是安全,因為類加載器都是從不同的目錄進行加載的(網上有介紹三個最基礎的類加載器Bootstrap ClassLoader\Extension ClassLoader\System ClassLoader加載的默認目錄,這里不多介紹),所以用這種目錄的方式來進行權限管理,常用都是使用classpath系統變量,如果我想自己定義一個加載目錄,那么就需要實現自己的類加載器,進行相應的權限管理。
另外這個抽象類,實現了只是從本地進行加載類的方式,如果需要進行遠程加載類,那么也需要實現自己的類加載器。
這里是類加載器的基本功能介紹,里面還有如何加載native Library 方法、class文件/包名相關安全驗證等,在后續會繼續介紹。
?
?
JVM類加載機制(ClassLoader)源碼解析(2)
分類: java服務器 2011-12-13 15:21 4726人閱讀 評論(0)收藏 舉報我們來對defineClass這個方法進行解析,該方法比較復雜,首先如圖:
該方法主要是通過一個字節的數組,對該數據進行解析、驗證、二進制碼格式校驗。通過拋出的ClassFormatError的這個異常來看,也是驗證這個類的格式是否符合JVM的規范。
最終是將一個字節數組轉換成類實例(注意不是實例對象,是有區別的)。
目前該方法已被替代,注意上圖里的注釋。
目前新方法,如下圖:
第一個參數,其實就是類名(包括整個包名在內)。
現在對該方法進行詳細的解析,首先看下check方法,如圖:
非常簡單,就是檢測classLoader是否初始化。
preDefineClass方法,如圖:
checkName檢測類名是否有效(去除數組類型的判斷),禁止以java.為前綴包名。如果沒有保護域,則獲取默認保護域;最后是進行數字簽名的驗證。
主要看下數字驗證方法,checkCerts方法,如圖:
數字驗證的方式也很簡單,如果存在就比較當前保護域里的數字簽名與之前的數字簽名是否匹配;反之就在package2certs對象(就是一個hashtable)里緩存起來。
這里的數字簽名是從CodeSource這個類型的對象來獲取,也就是代碼源里的簽名。
compareCerts方法就是對比兩個數字簽名是否匹配(方法很簡單,這里就不用截圖了)。這里主要是針對于類安全考慮,防止有人惡意修改類文件,采用的是通用的java安全框架來實現的。以后會在java安全的源碼分析里在進行詳細介紹。
defineClassSourceLocation方法,如圖:
非常簡單,就是獲取代碼基的路徑。
然后就是開始調用本地解析方法defineClass1進行處理(類似于findLoadedClass方法處理)。如果拋出ClassFormatError異常,在給一次機會(調用方法defineTransformedClass),重新傳輸一次類的二進制碼做解析;如果還是失敗,就拋出ClassFormatError異常類型。
最后是執行postDefineClass方法,如圖:
這里就是用之前比較過的數字簽名,對類進行簽名操作(也就是類似于加密操作)
這里需要注意的是:
defineClass方法與loadClass方法是兩種不同加載類的實現。
區別在于:
前者是有嚴格的安全機制,輸入源是一個二進制碼;后者很簡單就是一個本機的加載方式,輸入源來自本地存放的class文件。
前者比較適合于網絡遠程加載類,因為需要進行安全控制;后者是一個比較基于自定義目錄加載的實現。
前者在實現時主要是findClass方法與之配合使用;而后者主要是要設置加載的URL(也就是路徑)。
這里非常明顯地體現出了java的核心思想(嚴格地來講應該是核心特性),安全和網絡。
?
JVM類加載機制(ClassLoader)源碼解析(3)
分類: java服務器 2011-12-13 19:00 4743人閱讀 評論(0)收藏 舉報java的類加載器,還有一個特殊的功能,就是加載本地庫。這個功能是與關鍵字native是有關系的。簡單地說就是調用C++/C的本地庫(windows是后綴為.dll,linux下是后綴為.so)。
調用的地方是使用System這個類,其中有兩個方法如下:
load(String filename)
loadLibrary(String libname)
一個是根據文件名,一個根據lib名。注意文件名不等同于lib名,不然寫錯了,會報加載失敗信息。
System類是一個很特殊的類,與底層交互較多,會看到很多native關鍵字,到時候在會在解析System和Runtime時再詳細介紹。
回到之前的主題,這兩個方法最終都是調用到了ClassLoader.loadLibrary(fromClass, name,isAbsolute)。該方法如圖:
說明下參數:
fromClass這個參數是當前調用者的類實例,例如:
public class Test{
?? ? ? ? ? ?public void load(){
?? ? ? ? ? ? ? ? ??System.loadLibrary("1232");
?? ? ? ? ? ?}
}
這時fromClass參數就是Class<Test>。
isAbsolute參數很簡單,指是否是規則路徑(例如:../../test.java)。
name參數有可能是文件名方式,也有可能是libname方式。
該方法第一階段就是基于DownloadManager判斷,如果JRE沒有完成并且當前還有線程下載時,就會調用DownloadManager類的靜態方法downloadFile。
DownloadManager類,這個屬于sun.jkernel包下的,與JRE組件有關,簡單地說就是安裝了JRE的存放路。
有時候JRE是在嵌入的位置就是JAVA_HOME/jre,有時候是獨立位置,特別是在windows操作系統下安裝時,會提示安裝JRE這時可以隨時選擇存放路徑。
JVM初始化完成后會調用此類,另外DownloadManager這個類也支持命令行的操作。以后會進行更多的詳細介紹。
因為較為復雜,很多用了調用本地方法,所以我這里只簡單介紹。
java.library.path是java的系統變量,其值等于系統變量里的PATH。
sun.boot.library.path是java系統變量,其值等于JAVA_HOME/jre/bin。
主要是通過4個過程來加載本地庫。
第1步驟就是判斷是否是規則路徑,為true,就直接對規則路徑進行轉換后加載,如果加載失敗直接拋出異常并執行步驟5;反之執行步驟2。
?? ? ? ? ? ? ? ?規則路徑就是使用了.或..這樣符號。
第2步驟如果類加載器不為空,從類加載里獲取libname信息,
?? ? ? ? ? ? ? 但是抽象類ClassLoader是一直返回NULL的(可查看ClassLoader.findLibrary方法),所以基本上都會直接執行步驟3(除非是自定義類加載器,重寫了此方法)。
第3步驟首先從sun.boot.library.path里的全部路徑下搜索libname進行加載,如果成功執行步驟5;反之執行步驟4。
第4步驟首先判斷類加載器是否為空,不為空時,將從java.library.path里的全部路徑下搜索libname進行加載,不管成功與否,都會執行步驟5。
第5步驟執行完畢。
?? 另外本地庫名稱(已經加載過的)在代碼中有三個存放屬性,如下:
?? ?// All native library names we've loaded.
?? ?private static Vector loadedLibraryNames = new Vector();
?? ?// Native libraries belonging to system classes.
?? ?private static Vector systemNativeLibraries = new Vector();
?? ?// Native libraries associated with the class loader.
?? ?private Vector nativeLibraries = new Vector();
加載之后存放相關信息使用NativeLibrary類來保存的,該加載機制與JNI技術有密切聯系,也使用下JNI會對該加載功能有更深入的了解。
System與Runtime源碼解析
分類: java服務器 2011-12-14 14:13 4095人閱讀 評論(0) 收藏 舉報在類加載器里提到了System與Runtime類,這里就趁熱打鐵來對這兩個源碼進行解析,因為System與Runtime關聯很緊密,所以就一起來解析吧。
首先來看看System類提供的幾個特性:
1、standard input, standard output, and error output streams
2、訪問擴展屬性和java的環境變量
3、加載本地內庫
4、提供一個arraycopy的復制功能
5、獲取Console對象
6、獲取和設置SecurityManager對象
7、獲取本地庫文件mapLibraryName方法
?? ? ?該方法示例:System.out.println(System.mapLibraryName("awt")); 打印結果為awt.dll,這個文件存放的路徑在JAVA_HOME/jre/bin目錄下,大家可以自己試試其他的。
8、JVM退出(exit)
看下Runtime類提供的幾個特性:
1、一個JVM對應一個Runtime對象(single)
2、允許訪問和調用其他應用程序
3、擴展ShutdownHook
4、獲取內存使用相關信息
5、加載本地內庫
6、JVM退出(exit)
首先分析下System類,該類有個重要的方法,如下圖:
該方法是被JVM調用的,很奇怪吧。當時我也沒想明白,這個方法是private,怎么被調用到呢?后來仔細分析后發現,注意這個方法,如下:
private static native void registerNatives();
?? ?static {
?? ? ? ?registerNatives();
?? ?}
該方法會將initializeSystemClass方法映射到本地方法里,方便JVM調用;這里很重要,JNI方便java去調用C++/C的動態連接庫。而該方法是讓C++/C能調用到java方法。
至于registerNatives方法做了什么更具體的事情,可以去查看源碼。
我們來具體分析下initalizeSystemClass做那些事情:
1、初始化out、in、err等流
2、初始化環境變量Properties
3、初始化信號量,Terminator.setup();
4、sun.misc.VM.initializeOSEnvironment();
?? ? ??
?? ? ? OSEnvironment代碼如下:
?? ? ??
?? ? ? 這里就很明顯了,主要是設置些錯誤模式標識,JVM如何處理這樣的錯誤(目前是四種:臨界區錯誤、文件錯誤、自動修復內存對齊、一般的故障保護)。
?? ? ??
5、sun.misc.VM.maxDirectMemory();
?? ? ? 該方法在VM很簡單,就是直接return directMemory,該參數的設置與-XX:MaxDirectMemorySize=<size>有關
6、sun.misc.VM.allowArraySyntax();
?? ? ? 在VM也很簡單,也是直接返回return?allowArraySyntax
7、sun.misc.VM.booted();
?? ? ? 在VM里是將booted賦值為true。
個人認為5、6兩個步驟是沒有必要的,不知道為什么要調用下。
另外就是如何驗證initalizeSystemClass是JVM調用的,很簡單,可以參考下out/in/err等屬性是如何初始化的。我自己寫了模擬例子,如下:
class AB{
}
final class AB_System{
public static final AB a=nullAB();
private static AB nullAB(){
if (System.currentTimeMillis() > 0) {
? ?return null;
}
throw new NullPointerException();
}
static{
System.out.println("ab_system static");
}
private AB_System(){
}
public static void print(){
System.out.println("abaddd_dadfa");
}
}
public class Test_System {
public static void main(String[] args) {
System.out.println(AB_System.a==null);
}
}
運行結果是true。
再來分析下Runtime類。
因源碼里有較多的native方法,所以邏輯比較簡單,
這里就是主要是exec這樣的方法,可以找些例子操作體驗下,就行了。
與之相關的時Process類,這個類就是外部被調用的應用程序,在java里的一個代理對象(也可以叫做交互的接口對象,例如返回相關的運行狀態等),后面會對這個類進行解析。
還有就是掌握強制終止和正常終止的區別。
關于兩者之間的關聯
個人認為差別不是很大,因為有很多相同點(System里也有需要調用Runtime里的方法)。
分成兩個類,還是因為職責分開吧,System比較傾向于程序員使用(是一個工具類,不能被實例化),而Runtime(是一個single object)更傾向于與JVM和其他應用程序交互。
這兩個類主要用于的場景如下:
如何安全的關閉應用程序(換句話說就是java程序退出或者JVM退出),可能很少在寫java程序時(特別是java的開源項目眾多),要考慮如何關閉JVM的
自定義的SecurityManager類
增加Hook
加載自己的動態鏈接庫等
總結
以上是生活随笔為你收集整理的JVM类加载机制(ClassLoader)源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分析BootstrapClassLoad
- 下一篇: 如何用数学和化学方法测量英国海岸线的长度