http://blog.csdn.net/chenyi8888/article/details/7066569
其實JVM類加載機制,簡單地說就是類管理,也就是我們生成的class文件。
三個步驟:裝載(load)、鏈接(link)、解析(Resolve)、還有初始化(Initialize)
關(guān)于網(wǎng)上有很多講解加載的方式,和調(diào)用的方式,還是幾個基本的classLoader,這里就不在多描述了。
這里更多的是從源碼上來講解,達(dá)到理論結(jié)合實際。
首先是ClassLoader這個抽象類,這個是實現(xiàn)自定義類的基礎(chǔ)。那么在調(diào)用的時候,首先都是調(diào)用loadClass這個方法,如圖:
該方法調(diào)用的是一個重載方法loadClass(name,false)方法如圖:
第一行注釋很清楚的表示,從當(dāng)前的類加載器里加載class文件,如果已經(jīng)加載,就直接返回;不存在就加載;如果返回NULL,表示class文件不在這個類加載器的范圍內(nèi)。
這是就會執(zhí)行if(c==null)下面的方法,就會去找父加載器(類加載器的整個結(jié)構(gòu)是樹形的,這里不多介紹),如果父加載器不存在,就直接找到根加載器(根加載器一定存在)。
我們現(xiàn)在開始解析每個被調(diào)用方法。
首先看下findLoadedClass方法,如圖:
該方法首先,校驗類名是否正確(類似于 test.demo.Test.class),校驗成功后,調(diào)用本地方法findLoadedClass0方法。
接著是parent.loadClass(name,false)方法,該方法是重復(fù)的,只是對象不同而已,就不介紹了。
接著是findBootstrapClassOrNull方法,如圖:
方式與findLoadedClass方法一樣,就是本地調(diào)用是findBootstrapClass方法。
如果此時類還不存在,有一個findClass方法的調(diào)用。代碼如圖:
這里看上去覺得很奇怪,直接就拋一個空異常了,其實根據(jù)注釋所說,就是自定義的類加載器需要實現(xiàn)這種方法。
為什么要實現(xiàn)這個方法呢?原因就是有些場景會需要通過網(wǎng)絡(luò)傳輸?shù)姆绞竭M(jìn)行加載類(該類其實是在遠(yuǎn)程服務(wù)器上的),在很多地方就會用到,例如遠(yuǎn)程調(diào)用等技術(shù)。
具體的實現(xiàn)例子,在URLClassLoader里就有體現(xiàn),這里暫不介紹。
最后是調(diào)用了resolveClass方法,代碼如圖:
這個方法很簡單,主要是重新進(jìn)行解析類,該解析主要是對所有的屬性/方法調(diào)用是否存在、相應(yīng)的權(quán)限(如private、public等)進(jìn)行驗證。那么為什么默認(rèn)都是傳入false呢?
那是因為是否需要更強的安全機制的檢測,
另一個情況是類加載是無序的,會導(dǎo)致類鏈接不成功。
這里有個狀態(tài)位的控制,主要是可能有些場景需要更嚴(yán)格的對類進(jìn)行驗證(目前我還沒有使用到這樣的場景)。
這里要注意的是:
類為什么是樹形結(jié)構(gòu),主要就是安全,因為類加載器都是從不同的目錄進(jìn)行加載的(網(wǎng)上有介紹三個最基礎(chǔ)的類加載器Bootstrap ClassLoader\Extension ClassLoader\System ClassLoader加載的默認(rèn)目錄,這里不多介紹),所以用這種目錄的方式來進(jìn)行權(quán)限管理,常用都是使用classpath系統(tǒng)變量,如果我想自己定義一個加載目錄,那么就需要實現(xiàn)自己的類加載器,進(jìn)行相應(yīng)的權(quán)限管理。
另外這個抽象類,實現(xiàn)了只是從本地進(jìn)行加載類的方式,如果需要進(jìn)行遠(yuǎn)程加載類,那么也需要實現(xiàn)自己的類加載器。
這里是類加載器的基本功能介紹,里面還有如何加載native Library 方法、class文件/包名相關(guān)安全驗證等,在后續(xù)會繼續(xù)介紹。
java的類加載器,還有一個特殊的功能,就是加載本地庫。這個功能是與關(guān)鍵字native是有關(guān)系的。簡單地說就是調(diào)用C++/C的本地庫(windows是后綴為.dll,linux下是后綴為.so)。
調(diào)用的地方是使用System這個類,其中有兩個方法如下:
load(String filename)
loadLibrary(String libname)
一個是根據(jù)文件名,一個根據(jù)lib名。注意文件名不等同于lib名,不然寫錯了,會報加載失敗信息。
System類是一個很特殊的類,與底層交互較多,會看到很多native關(guān)鍵字,到時候在會在解析System和Runtime時再詳細(xì)介紹。
回到之前的主題,這兩個方法最終都是調(diào)用到了ClassLoader.loadLibrary(fromClass, name,isAbsolute)。該方法如圖:
說明下參數(shù):
fromClass這個參數(shù)是當(dāng)前調(diào)用者的類實例,例如:
public class Test{
public void load(){
System.loadLibrary("1232");
}
}
這時fromClass參數(shù)就是Class<Test>。
isAbsolute參數(shù)很簡單,指是否是規(guī)則路徑(例如:../../test.java)。
name參數(shù)有可能是文件名方式,也有可能是libname方式。
該方法第一階段就是基于DownloadManager判斷,如果JRE沒有完成并且當(dāng)前還有線程下載時,就會調(diào)用DownloadManager類的靜態(tài)方法downloadFile。
DownloadManager類,這個屬于sun.jkernel包下的,與JRE組件有關(guān),簡單地說就是安裝了JRE的存放路。
有時候JRE是在嵌入的位置就是JAVA_HOME/jre,有時候是獨立位置,特別是在windows操作系統(tǒng)下安裝時,會提示安裝JRE這時可以隨時選擇存放路徑。
JVM初始化完成后會調(diào)用此類,另外DownloadManager這個類也支持命令行的操作。以后會進(jìn)行更多的詳細(xì)介紹。
因為較為復(fù)雜,很多用了調(diào)用本地方法,所以我這里只簡單介紹。
java.library.path是java的系統(tǒng)變量,其值等于系統(tǒng)變量里的PATH。
sun.boot.library.path是java系統(tǒng)變量,其值等于JAVA_HOME/jre/bin。
主要是通過4個過程來加載本地庫。
第1步驟就是判斷是否是規(guī)則路徑,為true,就直接對規(guī)則路徑進(jìn)行轉(zhuǎn)換后加載,如果加載失敗直接拋出異常并執(zhí)行步驟5;反之執(zhí)行步驟2。
規(guī)則路徑就是使用了.或..這樣符號。
第2步驟如果類加載器不為空,從類加載里獲取libname信息,
但是抽象類ClassLoader是一直返回NULL的(可查看ClassLoader.findLibrary方法),所以基本上都會直接執(zhí)行步驟3(除非是自定義類加載器,重寫了此方法)。
第3步驟首先從sun.boot.library.path里的全部路徑下搜索libname進(jìn)行加載,如果成功執(zhí)行步驟5;反之執(zhí)行步驟4。
第4步驟首先判斷類加載器是否為空,不為空時,將從java.library.path里的全部路徑下搜索libname進(jìn)行加載,不管成功與否,都會執(zhí)行步驟5。
第5步驟執(zhí)行完畢。
另外本地庫名稱(已經(jīng)加載過的)在代碼中有三個存放屬性,如下:
// 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();
加載之后存放相關(guān)信息使用NativeLibrary類來保存的,該加載機制與JNI技術(shù)有密切聯(lián)系,也使用下JNI會對該加載功能有更深入的了解。
在類加載器里提到了System與Runtime類,這里就趁熱打鐵來對這兩個源碼進(jìn)行解析,因為System與Runtime關(guān)聯(lián)很緊密,所以就一起來解析吧。
首先來看看System類提供的幾個特性:
1、standard input, standard output, and error output streams
2、訪問擴展屬性和java的環(huán)境變量
3、加載本地內(nèi)庫
4、提供一個arraycopy的復(fù)制功能
5、獲取Console對象
6、獲取和設(shè)置SecurityManager對象
7、獲取本地庫文件mapLibraryName方法
該方法示例:System.out.println(System.mapLibraryName("awt")); 打印結(jié)果為awt.dll,這個文件存放的路徑在JAVA_HOME/jre/bin目錄下,大家可以自己試試其他的。
8、JVM退出(exit)
看下Runtime類提供的幾個特性:
1、一個JVM對應(yīng)一個Runtime對象(single)
2、允許訪問和調(diào)用其他應(yīng)用程序
3、擴展ShutdownHook
4、獲取內(nèi)存使用相關(guān)信息
5、加載本地內(nèi)庫
6、JVM退出(exit)
首先分析下System類,該類有個重要的方法,如下圖:
該方法是被JVM調(diào)用的,很奇怪吧。當(dāng)時我也沒想明白,這個方法是private,怎么被調(diào)用到呢?后來仔細(xì)分析后發(fā)現(xiàn),注意這個方法,如下:
private static native void registerNatives();
static {
registerNatives();
}
該方法會將initializeSystemClass方法映射到本地方法里,方便JVM調(diào)用;這里很重要,JNI方便java去調(diào)用C++/C的動態(tài)連接庫。而該方法是讓C++/C能調(diào)用到j(luò)ava方法。
至于registerNatives方法做了什么更具體的事情,可以去查看源碼。
我們來具體分析下initalizeSystemClass做那些事情:
1、初始化out、in、err等流
2、初始化環(huán)境變量Properties
3、初始化信號量,Terminator.setup();
4、sun.misc.VM.initializeOSEnvironment();
OSEnvironment代碼如下:
這里就很明顯了,主要是設(shè)置些錯誤模式標(biāo)識,JVM如何處理這樣的錯誤(目前是四種:臨界區(qū)錯誤、文件錯誤、自動修復(fù)內(nèi)存對齊、一般的故障保護(hù))。
5、sun.misc.VM.maxDirectMemory();
該方法在VM很簡單,就是直接return directMemory,該參數(shù)的設(shè)置與-XX:MaxDirectMemorySize=<size>有關(guān)
6、sun.misc.VM.allowArraySyntax();
在VM也很簡單,也是直接返回return allowArraySyntax
7、sun.misc.VM.booted();
在VM里是將booted賦值為true。
個人認(rèn)為5、6兩個步驟是沒有必要的,不知道為什么要調(diào)用下。
另外就是如何驗證initalizeSystemClass是JVM調(diào)用的,很簡單,可以參考下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);
}
}
運行結(jié)果是true。
再來分析下Runtime類。
因源碼里有較多的native方法,所以邏輯比較簡單,
這里就是主要是exec這樣的方法,可以找些例子操作體驗下,就行了。
與之相關(guān)的時Process類,這個類就是外部被調(diào)用的應(yīng)用程序,在java里的一個代理對象(也可以叫做交互的接口對象,例如返回相關(guān)的運行狀態(tài)等),后面會對這個類進(jìn)行解析。
還有就是掌握強制終止和正常終止的區(qū)別。
關(guān)于兩者之間的關(guān)聯(lián)
個人認(rèn)為差別不是很大,因為有很多相同點(System里也有需要調(diào)用Runtime里的方法)。
分成兩個類,還是因為職責(zé)分開吧,System比較傾向于程序員使用(是一個工具類,不能被實例化),而Runtime(是一個single object)更傾向于與JVM和其他應(yīng)用程序交互。
這兩個類主要用于的場景如下:
如何安全的關(guān)閉應(yīng)用程序(換句話說就是java程序退出或者JVM退出),可能很少在寫java程序時(特別是java的開源項目眾多),要考慮如何關(guān)閉JVM的
自定義的SecurityManager類
增加Hook
加載自己的動態(tài)鏈接庫等
|