《深入理解Java虛擬機器》個人讀書總結——虛擬機器類載入機制__java

來源:互聯網
上載者:User

我們都知道Java虛擬機器是用來運行我們編譯好的.class檔案的,class檔案中夾帶類的各種資訊,虛擬機器要運行這些檔案,第一件事就是要載入到虛擬機器中,這就引出了這次總結的問題——虛擬機器是如何載入這些class檔案的。載入後虛擬機器是怎麼處理檔案中夾帶的資訊的。 類載入機制

首先什麼事類載入機制,這裡有必要先介紹一下概念:
虛擬機器把描述類的資料從CLass檔案載入到記憶體中,並對資料進行校正、轉換解析和初始化,最終形成可以被虛擬機器直接使用的java類型,這就是虛擬機器的類載入機制。 類載入的時機

類從被載入到虛擬機器開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和卸載7個階段。它的生命週期如圖所示:

其中載入、驗證、準備、初始化和卸載這5個階段的順序是確定的,類的載入過程必須按照這種順序按部就班地開始,而解析階段則不一定,為了支援java語言的運行時綁定,解析動作放在了類初始化之後。 類載入的全過程 載入

在載入階段,虛擬機器需要完成以下3件事情:
1)通過一個類的全限定名來擷取定義此類的二進位位元組流。
2)將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構。
3)在記憶體中產生一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的訪問入口。
這是虛擬機器規範的三點,但是並沒有明確指出具體要怎麼做。
例如第一條:虛擬機器要一個二進位位元組流,但是沒有明確指明要從哪裡擷取,怎麼樣擷取,這就可以玩出很多花樣來了。最常見的如從壓縮包中讀取,jar包啊、war包之類的。還有jsp檔案直接產生對應的Class類。
載入階段完成之後,虛擬機器外部的二進位位元組流就按照虛擬機器所需的格式儲存在方法區之中,方法區中的資料存放區格式由虛擬機器實現自行定義,虛擬機器規範未規定此地區的具體資料結構。然後在記憶體中執行個體化一個java.lang.Class類的對象(並沒有明確規定是在java堆中,在HotSpot虛擬機器中,Class對象是被放在了方法區裡面,雖然它也是對象) 驗證

驗證是串連階段的第一步,這一階段是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
在驗證階段大致上會完成下面4個階段的檢驗動作:檔案格式校正、中繼資料驗證、位元組碼驗證、符號引用驗證。
1)檔案格式校正
要驗證位元組流是否符合Class檔案格式的規範,並且能被目前的版本的虛擬機器處理。這個部分是直接操作位元組流的,通過驗證以後位元組流就進入到記憶體中進行儲存,之後的動作也就基於方法區的儲存結構進行了,不再直接操作位元組流。
2)中繼資料驗證
這要做的是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言規範的要求。對類的中繼資料資訊進行語義校正,這些資料主要描述資料的資料,通俗來說是描述代碼之間的關係的資料。如子父類之間的關係等等。。。
3)位元組碼驗證
這一步是最複雜的,主要目的是通過資料流和控制流程分析,確定程式語義是合法的、符合邏輯的。這裡校正的是類的方法體。
4)符號引用驗證
最後一階段的校正發生在虛擬機器將符號引用轉化成直接引用的時候,這個轉化動作將在串連的第三個階段———解析階段中發生。符號引用驗證可以看做是對類自身以外(常量池中的各種符號引用)的資訊進行匹配性校正。它的目的是確保解析動作能正常執行,如果無法正常通過符號引用驗證,那麼將會拋出一個java.lang.IncompatibleClassChangeError異常的子類,如常見的NoSuchMethodError、IllegalAccessError。 準備

準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些變數所使用的記憶體都將在方法區中進行分配。要注意的是,在這個階段進行記憶體配置的只有static變數,並不包括執行個體變數,執行個體變數將會在對象執行個體化時隨著對象一起分配在java堆中。設定的初始值通常情況下是資料類型的零值,但是如果類變數被設定成final了,編譯時間會在欄位屬性工作表中產生ConstantValue屬性,在準備階段虛擬機器就會根據ConstantValue設定。 解析

解析階段是虛擬機器將常量池的符號引用替換成直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。
直接引用:直接指向目標的指標、相對位移量或是一個能間接到目標的控制代碼。
解析動作主要針對類或介面、欄位、類方法、介面方法、方法類型、方法控制代碼和調用點限定符7類符號引用進行,分別對應於常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodred_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info7種常量類型。
1)類或介面的解析
假設有類D,如果要解析一個符號引用N成類或介面C的直接引用:
如果C不是一個數群組類型,虛擬機器會把代表N的全限定名傳遞給D的類載入器去載入類C。
如果C是一個數群組類型,並且數組的元素類型為對象,N的描述符類似”[Ljava/lang/Integer”,會按照上一條規則載入,如果N的描述符類似”java.lang.Integer”,那就會由虛擬機器產生一個代表此數組維度和元素的數組對象。
在完成前還要進行符號引用驗證,確認D是否具備對C的存取權限。不具備則拋出”IllegalAccessError”異常。
2)欄位解析
首先對欄位表class_index項中索引CONSTANT_Class_info符號引用進行解析,也就是欄位所屬的類或介面的符號引用。如果解析成功,那將這個欄位所屬的類或介面用C表示,虛擬機器還會對C進行後續欄位的搜尋。
如果C本身就包含有相配的,或與C的相關的有繼承關係的進行遞迴尋找到有相配的,返回直接引用。返回成功還有許可權驗證。
找不到則拋出NoSuchFieldError異常。
3)類方法解析
類方法解析和欄位解析差不多,同樣的查不到也會拋出NoSuchMethodError。
4)介面方法解析
從介面方法表class_index項中索引中找,如果找到的C是個類而不是介面,直接拋異常IncompatibleClassChangeError。
後面的找法和類方法解析基本一樣。找不到拋異常NoSuchMethodError。
此外,介面中的方法因為都是預設public修飾的,所以不存在存取權限的問題,自然也不會拋出IllegalAccessError異常。 初始化

類初始化階段是類載入的最後一步,初始化階段是執行類構造器<clinit>()方法的過程。
<clinit>()方法是有編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊(static{}塊)中的語句合并產生的。
虛擬機器會保證一個類的()方法在多線程環境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。如果一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個進程阻塞。

既然說到類載入機制,就必不可少地要說到類載入器了。 類載入器

虛擬機器設計團隊把類載入器階段中的”通過一個類的全限定名來擷取描述此類的二進位位元組流”這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何取擷取所需要的類。實現這個動作的代碼模組稱為”類載入器”。對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。通俗來說就是兩個類是否相等,先得以這兩個類是不是同一類載入器載入為前提。從java虛擬機器的角度來看,只存在兩種不同的類載入器:一種是啟動類載入器(BootStrap ClassLoader),這個類載入器是使用C++語言實現的,是虛擬機器的一部分;另一種就是所有其他的類載入器,這些是獨立於虛擬機器外部的,都是由java語言實現的,並且都繼承自java.lang.ClassLoader。
從java開發人員角度來看,以下3種是最常使用的。
1)啟動類載入器(BootStrap ClassLoader)
負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機器識別的類庫載入到虛擬機器記憶體中。
2)擴充類載入器(Extension ClassLoader)
負責載入<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫。開發人員可直接使用。
3)應用程式類載入器
ClassLoader的getSystemClassLoader()的傳回值。負責載入使用者類路徑(ClassPath)上所指定的類庫。
我們的應用程式都是由這3種類載入器互相配合載入的,它們之間的關係如圖所示:
雙親委派模型

這就是我們老生常談的類載入器的雙親委派模型。要求除了頂層的啟動類載入器外,其餘的類載入器都應當有自己的父類載入器。這裡類載入器之間的父子關係一般不會以繼承關係來實現,而是使用組合的關係來複用父載入器的代碼。
雙親委派模型的工作過程是這樣的:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父類載入器反饋自己無法完成這個載入請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入。
使用雙親委派模型的好處顯而易見的就是java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。它的實現很簡單,我們來看看原始碼

protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            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) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    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;        }    }

步驟:
先檢查是否已經被載入過,若沒有載入則調用父載入器的loadClass()方法,若父載入器為空白則預設使用啟動類載入器作為父載入器。如果父類載入器失敗,拋出異常後再調用自己的findClass()方法進行載入。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.