前面的學習我們知道了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, booleanresolve)throwsClassNotFoundException
{//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);
}returnc;
}
loadClass有兩個參數,第一個參數是類的全限定名,第二個參數就是我們要說的重點,這個參數為true的時候表示,loadClass方法會執行resolveClass的方法,這個方法就是将類中的符号引用替換為直接引用。最終調用的方法是一個本地方法 resolveClass0。
這裡還有一點需要注意,Class.forName這個靜态的方法我們也常用來加載class檔案的位元組碼,那它和classLoader有什麼差別?
差別就在于是否執行resolveClass這個方法,Class.forName總是承諾将符号連接配接進行連接配接和初始化,而loadClass沒有這樣的承諾。
總結:
第一趟掃描,在類被裝載時進行,校驗class檔案的内部結構,保證能夠被正常安全的編譯
第二趟和第三趟在連接配接的過程中進行,這兩趟基本上是文法校驗,詞法校驗
第四趟是解析符号引用和直接引用時進行的,這次校驗确認被引用的類,字段以及方法确實存在