JVM:加載、鏈接和初始化
JVM要解釋Java字節碼,就必須對所需的類和接口執行如下3步操作:
(1) 加載:JVM在加載類時,會查找該類或該接口的二進制表示,并根據找到的二進制表示(通常是由Java編譯器創建的類文件)創建一個Class對象。該Class對象中封裝了類或接口的運行時狀態。
(2) 鏈接:鏈接這一過程是指取得已加載的類或接口、結合JVM運行時環境、準備執行該類或該接口。
(3) 初始化:初始化是指JVM調用該類或該接口的初始化方法。
- 第一步
啟動一個單機Java程序時,JVM首先做的是另外創建一個Class對象,用于表示包含public static void main(String [ ] args)方法的Java類。然后JVM會鏈接并初始化該Java類,調用main()方法,并用main()方法驅動所引用的其他類和接口的加載、鏈接和初始化過程。 - 加載
加載過程是由類加載器完成的,該加載器是ClassLoader的子類,并且該類加載器會對所加載的類或接口進行一些校驗檢查。當表示已編譯類或接口的二進制數據有錯,則類或接口使用的類文件格式版本不被支持,類加載器找不到類或接口的定義,或者如果出現類循環,都會拋出異常。類循環是指類或接口的父類是其自身的情況。
類加載器一般有兩種類型:由JVM提供的引導類加載器(bootstrap class loader)和用戶定義的類加載器。用戶定義的類加載器也是Java的ClassLoader類的子類,用于從非標準的、用戶定義的源創建Class對象,以便提高安全性。例如,從加密文件中提取Class對象。一個加載器可以將部分甚至整個加載過程委托給另一個加載器。最終生成Class對象的加載器稱為定義加載器(defining loader),而開始該加載過程的加載器稱為啟動加載器(initiating loader)。
使用默認引導類加載器的加載過程如下:根據所要加載的類文件,引導類加載器會判斷自身是否已經成為該類的啟動加載器。如果是,則Class對象存在,加載器停止(注意,加載一個類并不等于創建該類的一個實例,這一步驟僅僅是在JVM中加入該類)。如果類還沒有加載,則加載器會搜索對應的類文件,并在找到后根據該類文件創建Class對象。如果找不到類文件,那么就會產生NoClassDefFoundError異常。
使用用戶定義類加載器時,整個加載過程稍有不同。與引導加載器一樣,用戶定義的加載器首先判斷自身是否已經成為目標類文件的啟動加載器。如果是,則 Class對象已經存在,加載器停止,而如果不是,用戶定義的加載器會調用loadClass()方法。loadClass()方法返回所需的類文件并將表示類的二進制字節裝配成ClassFile結構,然后調用defineClass()方法,由該方法從ClassFile結構創建Class對象。另外,loadClass()方法也可以將加載過程委托給另一個類加載器。 - 鏈接
鏈接過程的第一步是校驗需要鏈接的類文件。
Java類文件校驗
由于JVM與Java編譯器是完全分離的,因此,用來解釋類文件的JVM無法保證類文件的形式正確,甚至無法保證該文件確實由Java編譯器所生成。另一個問題在于繼承與類兼容性。如果給定類文件所表示的類繼承自另一個類文件表示的父類,那么JVM必須確保該子類的類文件與父類的類文件兼容。
JVM會校驗每個類文件是否滿足Java語言規范對類文件的約束,不過Java類校驗器與Java語言無關。用某些其他語言編寫的程序同樣也能編譯成類文件格式,編譯之后,該類文件也能通過校驗過程。
校驗過程分為4個步驟:
(1) 第一步由JVM加載類文件并檢查文件是否符合類文件的基本格式。類文件的長度必須準確。類文件必須確實表示類(檢查其中一個特殊數字)。常量池中不能包含任何不可識別的信息,并且每個屬性的長度正確。
(2) 校驗過程的第二步在文件鏈接時進行。這一步執行的操作包括確保final關鍵字約束的保留。這表示final類不能派生子類,final方法也不能被重寫。然后確保常量池中的元素符合Java語言的規定。驗證常量池中的所有字段和方法引用,并檢查每一個類(Object類除外)是否具有直接父類。
(3) 第三個校驗步驟也在鏈接階段進行。這一步檢查類文件中引用的每一個方法,確保符合Java語言對方法的規定。方法調用中參數的數量和類型必須正確。操作數棧必須總保持相同大小,并包含相同類型的值。局部變量在訪問前應當包含合適的值。必須為字段指定正確類型的值。
(4) 校驗的最后一步是處理第一次調用方法時出現的事件,并保證一切按規范進行。這些檢查包括:確保給定類中存在某個引用的字段或引用的方法,確認引用的字段或引用的方法具有正確的描述符,并確保一個方法在運行時能夠訪問該引用字段或引用方法。
準備
在校驗類文件之后,JVM準備初始化類,包括為類變量分配內存空間并設置為默認初始值。這些值是標準的默認值,例如int類型為0,Boolean類型為false等。在初始化階段,這些值會設為程序相關的默認值。
解析
在這一可選的步驟中,JVM把運行時常量池中引用的符號解析成具體值。 - 初始化
鏈接過程完成后,會調用靜態字段和靜態初始化器。靜態字段的值即使在類沒有實例化時也能夠訪問得到,而靜態初始化器用于單個表達式無法表示的靜態初始化。 JVM把所有這類初始化器收集到一個特殊的方法中。例如,類所有初始化器的集合就是初始化方法<clinit>。
不過,JVM在初始化一個類時不僅需要調用該類的初始化方法(只有JVM能夠調用),而且需要初始化所有的父類(即需要調用這些父類的<clinit>)。結果就是,總是需要最先初始化Object類。另外,包含應用程序main()方法的類總是要初始化
原文地址:http://blog.csdn.net/e5945/article/details/5648102
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!