天天看點

虛拟機類加載機制類加載時機類加載的過程類加載器

  虛拟機把描述類的資料從Class檔案加載到記憶體,并對資料進行校驗、轉換解析和初始化,最終形成可以被虛拟機直接使用的java類型,這就是虛拟機的類加載機制。在java語言裡,類型的加載、連接配接和初始化過程都是在程式運作期間完成的,這會令類加載時稍微增加一些性能開銷,但是會為java應用程式提供高度的靈活性,java裡天生可以動态擴充的語言特性就是依賴運作期動态加載和動态連接配接這個特點實作的。

對于初始化階段,虛拟機規範嚴格規定了有且隻有5種情況必須立即對類進行“初始化”(加載、驗證、準備是在此之前開始的):

遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。

使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。

當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類初始化。

當虛拟機啟動時,使用者需要指定一個要執行的主類,虛拟機會先初始化這個主類。

當使用JDK 1.7的動态語言時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

類的生命周期如下圖所示。加載、驗證、準備、初始化和解除安裝這5個階段的順序是确定的,類的加載過程必須按照這種順序按部就班地開始。

虛拟機類加載機制類加載時機類加載的過程類加載器

“加載”是“類加載”過程的一個階段。在加載階段,虛拟機需要完成以下3件事情:

通過一個類的全限定名來擷取定義此類的二進制位元組流。

将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構。

在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口。

加載階段與連接配接階段的部分内容是交叉進行的,加載階段尚未完成,連接配接階段可能已經開始。但這兩個階段的開始時間仍然保持着固定的先後順序。

驗證是連接配接階段的第一步,這一階段的目的是為了確定Class檔案的位元組流中包含的資訊符合目前虛拟機的要求,并且不會危害虛拟機自身的安全。驗證階段是非常重要的,這個階段是否嚴謹,直接決定了java虛拟機是否能夠承受惡意代碼的攻擊,從執行性能的角度上講,驗證階段的工作量在虛拟機的類加載子系統中又占了相當大的一部分。驗證階段大緻上會完成下面4個階段的檢驗動作:

檔案格式驗證:檢查格式,保證輸入的位元組流能正确地解析并存儲于方法區之内,格式上符合描述一個java類型資訊的要求。

中繼資料驗證:檢查語義,保證不存在不符合java語言規範的中繼資料資訊。

位元組碼驗證:檢查邏輯,整個驗證過程中最複雜的一個階段,通過資料流和控制流分析,确定程式語義是合法的、符合邏輯的,以保證被校驗類的方法在運作時不會做出危害虛拟機安全的事件。

符号引用驗證:對類自身以外的資訊進行比對性校驗,確定解析動作能正常執行。

正式為類變量配置設定記憶體并設定類變量初始值,這些變量所使用的記憶體都将在方法區中進行配置設定。

虛拟機将常量池内的符号引用替換為直接引用。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符号引用進行。

初始化階段是類加載過程的最後一步,

類加載器主要實作通過一個類的全限定名來擷取描述此類的二進制位元組流。對于任意一個類,都需要由加載它的類加載器和這個類本身一同确立其在java虛拟機中的唯一性,每一個類加載器都擁有一個獨立的類名稱空間。比較兩個類是否“相等”,隻有在這兩個類是由同一個類加載器加載的前提下才有意義。

Tomcat自定義了多個類加載器,這些類加載器按照經典的雙親委派模型來實作,其架構如下圖所示:

虛拟機類加載機制類加載時機類加載的過程類加載器

OSGi(Open Service Gateway Initiative)是OSGi聯盟制定的一個基于java語言的動态子產品化規範。OSGi在java程式員中最著名的應用案例就Eclipse IDE,另外還有許多大型的軟體平台和中間件伺服器都基于或聲明将會基于OSGi規範來實作。

OSGi的Bundle類加載器之間隻有規則,沒有固定的委派關系。例如,某個Bundle聲明了一個它依賴的Package,如果有其他Bundle聲明釋出了這個Package,那麼所有對這個Package的類加載動作都會委派給釋出它的Bundle類加載器去完成。不涉及某個具體的Package時,各個Bundle加載器都是平級關系,隻有具體使用某個Package和Class的時候,才會根據Package導入導出定義來構造Bundle間的委派和依賴。其架構如下圖所示:

虛拟機類加載機制類加載時機類加載的過程類加載器

從java虛拟機角度來講,隻存在兩種不同的類加載器:一種是啟動類加載器,使用C++語言實作,是虛拟機自身的一部分;另一種是所有其他類的加載器,由java語言實作,獨立于虛拟機外部,并且全都繼承自抽象類java.lang.ClassLoader。其模型架構如下圖所示:

虛拟機類加載機制類加載時機類加載的過程類加載器

雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,是以所有的加載請求最終都應該傳送到頂層的啟動類加載器中,隻有當父類加載器回報自己無法完成這個加載請求時,子加載器才會嘗試自己去加載。

與雙親委派模型一樣,關鍵在于隻有一個外部接口,相當于門面。

OSGi實作子產品化熱部署的關鍵則是它自定義的類加載器機制的實作。每一個程式子產品都有一個自己的類加載器,當需要更換一個Bundle時,就把Bundle連同類加載器一起換掉以實作代碼的熱替換。在OSGi環境下,類加載器不再是雙親委派模型中的樹狀結構,而是複雜的網狀結構。當收到類加載請求時,OSGi将按照下面的順序進行類搜尋:

将以java.*開頭的類委派給父類加載器加載。

否則,将委派清單名單内的類委派給父類加載器加載。

否則,将Import清單中的類委派給Export這個類的Bundle的類加載器加載。

否則,查找目前Bundle的ClassPath,使用自己的類加載器加載。

否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類加載器加載。

否則,查找Dynamic Import清單的Bundle,委派給對應的Bundle的類加載器加載。

否則,類查找失敗。