1. 程式人生 > >java之jvm學習筆記三(Class檔案檢驗器)

java之jvm學習筆記三(Class檔案檢驗器)

               前面的學習我們知道了class檔案被類裝載器所裝載,但是在裝載class檔案之前或之後,class檔案實際上還需要被校驗,這就是今天的學習主題,class檔案校驗器。

             class檔案 校驗器,保證class檔案內容有正確的內部結構,java虛擬機器的class檔案檢驗器在位元組碼執行之前對檔案進行校驗,而不是在執行中進行校驗
class檔案校驗器要進行四趟獨立的掃描來完成校驗工作

class檔案校驗器分成四趟獨立的掃描來完成校驗。

第一趟

在裝載位元組序列的時候進行,這個是校驗class檔案的結構的合法性,比如你使用windowns下的copy命令去合併一個.class檔案和一個jpg檔案的時候,在裝載這個class檔案的時候jvm會發現這個class檔案被刪改過,檔案的長度也不正確,而丟擲異常!
所以這次校驗是發生在二進位制資料上,

第二趟

掃描發生在方法區中,主要對於,語義,詞法和語法的分析,也就是檢查這個類是否能夠順利的編譯!

第三趟

位元組碼校驗
在這一趟的校驗中涉及兩個比較不好理解的概念,第一個是位元組碼流,第二個是棧幀.

執行位元組碼時,一次執行操作碼,java虛擬機器內構成了執行執行緒,而每個執行緒會有自己的java棧就是我們說的棧幀。每一個方法都有一個棧幀。
如果學過彙編的人理解這兩個概念會容易一點
位元組碼流=操作碼+運算元,在這裡可以看做彙編裡的偽指令+運算元,因為這裡的操作碼實際上就是給jvm識別的“彙編偽指令”,而運算元的概念和彙編裡的除了資料型別,並沒有多大的差異
重點來看一下棧幀,棧幀其實也很好理解,棧幀裡有區域性變數棧

運算元棧,這兩塊記憶體就是放資料的時機不同,運算元棧就是用來存放位元組碼指令執行的中間結果,結果或運算元,而區域性變數區,就是用來存區域性變數形參等,這個很好理解
這個位元組碼的校驗過程校驗的就是位元組碼流的合法過程,也就是校驗運算元+操作碼的合法性。

java的class檔案編碼我們之所以稱之為位元組碼,是因為每調條操作指令都只佔一個位元組,除了兩個例外情況,所有的操作碼和他們的運算元按位元組對齊,這使得位元組流在傳輸的時候跟小,更有優勢,這兩個例外是這樣一些操作碼,在操作碼和他們的運算元之間會天上一至三個位元組,以便運算元都按位元組對齊。

下面是一個圖,描述了棧幀的結構

第四趟

符號引用的校驗


由於大部分jvm的實現都是延遲載入或者說動態連結的,延遲載入的意思就是,jvm裝載某個類A時,如果A類裡有引用其他的類B,虛擬機器並不會把這個被引用B類也同時裝載入記憶體,而是等到執行到的時候才去裝載。
而這個被引用的B類在引用它的類A中的表現形式主要被登記在了符號表中,而第四趟的這個過程就是當需要用到被引用類B的時候,將被引用類B在引用類A的符號引用名改為記憶體裡的直接引用
所以第四趟發生的時間是不可預料的,而且發生在方法區中。總個這個過程稱之為動態連線
可以簡單的劃分為兩步
1.查詢被引用的類(有必要的話就載入它)
2.將符號引用替換為直接引用,例如一個指向類、欄位或方法的指標,下次再需要用到被引用類的時候直接運用直接引用,不需要再去裝載。

這個過程其實在ClassLoader類中的loadClass中就可以發現它的痕跡。我們先貼出loadClass這個方法實現,然後簡要的做一下分析

    protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name);
	if (c == null) {
	    try {
		if (parent != null) {
		    c = parent.loadClass(name, false);
		} else {
		    c = findBootstrapClass0(name);
		}
	    } catch (ClassNotFoundException e) {
	        // If still not found, then invoke findClass in order
	        // to find the class.
	        c = findClass(name);
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

loadClass有兩個引數,第一個引數是類的全限定名,第二個引數就是我們要說的重點,這個引數為true的時候表示,loadClass方法會執行resolveClass的方法,這個方法就是將類中的符號引用替換為直接引用。最終呼叫的方法是一個本地方法 resolveClass0。

這裡還有一點需要注意,Class.forName這個靜態的方法我們也常用來載入class檔案的位元組碼,那它和classLoader有什麼區別?

區別就在於是否執行resolveClass這個方法,Class.forName總是承諾將符號連線進行連線和初始化,而loadClass沒有這樣的承諾。

總結:

第一趟掃描,在類被裝載時進行,校驗class檔案的內部結構,保證能夠被正常安全的編譯
第二趟和第三趟在連線的過程中進行,這兩趟基本上是語法校驗,詞法校驗
第四趟是解析符號引用和直接引用時進行的,這次校驗確認被引用的類,欄位以及方法確實存在

writed by:keycoding