Java虛擬機類裝載:原理、實現與應用[資料]

一、引言
  Java虛擬機(JVM)的類裝載就是指將包含在類文件中的字節碼裝載到JVM中, 並使其成爲JVM一部分的過程。JVM的類動態裝載技術能夠在運行時刻動態地加載或者替換系統的某些功能模塊, 而不影響系統其他功能模塊的正常運行。本文將分析JVM中的類裝載系統,探討JVM中類裝載的原理、實現以及應用。
  
  二、Java虛擬機的類裝載實現與應用
  2.1 裝載過程簡介
  
  所謂裝載就是尋找一個類或是一個接口的二進制形式並用該二進制形式來構造代表這個類或是這個接口的class對象的過程,其中類或接口的名稱是給定了的。當然名稱也可以通過計算得到,但是更常見的是通過搜索源代碼經過編譯器編譯後所得到的二進制形式來構造。
  
  在Java中,類裝載器把一個類裝入Java虛擬機中,要經過三個步驟來完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:
  
  裝載:查找和導入類或接口的二進制數據;
  鏈接:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;
  校驗:檢查導入類或接口的二進制數據的正確性;
  準備:給類的靜態變量分配並初始化存儲空間;
  解析:將符號引用轉成直接引用;
  初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。
  至於在類裝載和虛擬機啓動的過程中的具體細節和可能會拋出的錯誤,請參看《Java虛擬機規範》以及《深入Java虛擬機》,它們在網絡上面的資源地址是: http://java.sun.com/docs/books/vmspec/2nd-edition/html/Preface.doc.html 和 http://www.artima.com/insidejvm/ed2/index.html。 由於本文的討論重點不在此就不再多敘述。
  
  2.2 裝載的實現
  
  JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader 是一個重要的Java運行時系統組件。它負責在運行時查找和裝入類文件的類。
  
  在Java中,ClassLoader是一個抽象類,它在包java.lang中,可以這樣說,只要瞭解了在ClassLoader中的一些重要的方法,再結合上面所介紹的JVM中類裝載的具體的過程,對動態裝載類這項技術就有了一個比較大概的掌握,這些重要的方法包括以下幾個:
  
  ①loadCass方法 loadClass(String name ,boolean resolve)其中name參數指定了JVM需要的類的名稱,該名稱以包表示法表示,如Java.lang.Object;resolve參數告訴方法是否需要解析類,在初始化類之前,應考慮類解析,並不是所有的類都需要解析,如果JVM只需要知道該類是否存在或找出該類的超類,那麼就不需要解析。這個方法是ClassLoader 的入口點。
  
  ②defineClass方法 這個方法接受類文件的字節數組並把它轉換成Class對象。字節數組可以是從本地文件系統或網絡裝入的數據。它把字節碼分析成運行時數據結構、校驗有效性等等。
  
  ③findSystemClass方法 findSystemClass方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用defineClass將字節數組轉換成Class對象,以將該文件轉換成類。當運行Java應用程序時,這是JVM 正常裝入類的缺省機制。
  
  ④resolveClass方法 resolveClass(Class c)方法解析裝入的類,如果該類已經被解析過那麼將不做處理。當調用loadClass方法時,通過它的resolve 參數決定是否要進行解析。
  
  ⑤findLoadedClass方法 當調用loadClass方法裝入類時,調用findLoadedClass 方法來查看ClassLoader是否已裝入這個類,如果已裝入,那麼返回Class對象,否則返回NULL。如果強行裝載已存在的類,將會拋出鏈接錯誤。
  
  2.3 裝載的應用
  
  一般來說,我們使用虛擬機的類裝載時需要繼承抽象類java.lang.ClassLoader,其中必須實現的方法是loadClass(),對於這個方法需要實現如下操作:(1) 確認類的名稱;(2) 檢查請求要裝載的類是否已經被裝載;(3) 檢查請求加載的類是否是系統類;(4) 嘗試從類裝載器的存儲區獲取所請求的類;(5) 在虛擬機中定義所請求的類;(6) 解析所請求的類;(7) 返回所請求的類。
  
  所有的Java 虛擬機都包括一個內置的類裝載器,這個內置的類庫裝載器被稱爲根裝載器(bootstrap ClassLoader)。根裝載器的特殊之處是它只能夠裝載在設計時刻已知的類,因此虛擬機假定由根裝載器所裝載的類都是安全的、可信任的,可以不經過安全認證而直接運行。當應用程序需要加載並不是設計時就知道的類時,必須使用用戶自定義的裝載器(user-defined ClassLoader)。下面我們舉例說明它的應用。
  
  public abstract class MultiClassLoader extends ClassLoader{
  ...
  public synchronized Class loadClass(String s, boolean flag)
  throws ClassNotFoundException
  {
  /* 檢查類s是否已經在本地內存*/
  Class class1 = (Class)classes.get(s);
  
  /* 類s已經在本地內存*/
  if(class1 != null) return class1;
  try/*用默認的ClassLoader 裝入類*/ {
  class1 = super.findSystemClass(s);
  return class1;
  }
  catch(ClassNotFoundException _ex) {
  System.out.println(">> Not a system class.");
  }
  
  /* 取得類s的字節數組*/
  byte abyte0[] = loadClassBytes(s);
  if(abyte0 == null)  throw new ClassNotFoundException();
  
  /* 將類字節數組轉換爲類*/
  class1 = defineClass(null, abyte0, 0, abyte0.length);
  if(class1 == null) throw new ClassformatError();
  if(flag)  resolveClass(class1); /*解析類*/
  
  /* 將新加載的類放入本地內存*/
  classes.put(s, class1);
  System.out.println(">> Returning newly loaded class.");
  
  /* 返回已裝載、解析的類*/
  return class1;
  }
  ...
  }
  
  三、Java虛擬機的類裝載原理
  前面我們已經知道,一個Java應用程序使用兩種類型的類裝載器:根裝載器(bootstrap)和用戶定義的裝載器(user-defined)。根裝載器是Java虛擬機實現的一部分,舉個例子來說,如果一個Java虛擬機是在現在已經存在並且正在被使用的操作系統的頂部用C程序來實現的,那麼根裝載器將是那些C程序的一部分。根裝載器以某種默認的方式將類裝入,包括那些Java API的類。在運行期間一個Java程序能安裝用戶自己定義的類裝載器。根裝載器是虛擬機固有的一部分,而用戶定義的類裝載器則不是,它是用Java語言寫的,被編譯成class文件之後然後再被裝入到虛擬機,並像其它的任何對象一樣可以被實例化。 Java類裝載器的體系結構如下所示:
  
 

  
圖1 Java的類裝載的體系結構

  
  Java的類裝載模型是一種代理(delegation)模型。當JVM 要求類裝載器CL(ClassLoader)裝載一個類時,CL首先將這個類裝載請求轉發給他的父裝載器。只有當父裝載器沒有裝載並無法裝載這個類時,CL才獲得裝載這個類的機會。這樣, 所有類裝載器的代理關係構成了一種樹狀的關係。樹的根是類的根裝載器(bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根裝載器以外的類裝載器有且僅有一個父裝載器。在創建一個裝載器時, 如果沒有顯式地給出父裝載器, 那麼JVM將默認系統裝載器爲其父裝載器。Java的基本類裝載器代理結構如圖2所示:
  

  
圖2 Java類裝載的代理結構


  
  下面針對各種類裝載器分別進行詳細的說明。
  
  根(Bootstrap) 裝載器:該裝載器沒有父裝載器,它是JVM實現的一部分,從sun.boot.class.path裝載運行時庫的核心代碼。
  
  擴展(Extension) 裝載器:繼承的父裝載器爲根裝載器,不像根裝載器可能與運行時的操作系統有關,這個類裝載器是用純Java代碼實現的,它從java.ext.dirs (擴展目錄)中裝載代碼。
  
  系統(System or Application) 裝載器:裝載器爲擴展裝載器,我們都知道在安裝JDK的時候要設置環境變量(CLASSPATH ),這個類裝載器就是從java.class.path(CLASSPATH 環境變量)中裝載代碼的,它也是用純Java代碼實現的,同時還是用戶自定義類裝載器的缺省父裝載器。
  
  小應用程序(Applet) 裝載器: 裝載器爲系統裝載器,它從用戶指定的網絡上的特定目錄裝載小應用程序代碼。
  
  在設計一個類裝載器的時候,應該滿足以下兩個條件:
  
  對於相同的類名,類裝載器所返回的對象應該是同一個類對象
  如果類裝載器CL1將裝載類C的請求轉給類裝載器CL2,那麼對於以下的類或接口,CL1和CL2應該返回同一個類對象:a)S爲C的直接超類;b)S爲C的直接超接口;c)S爲C的成員變量的類型;d)S爲C的成員方法或構建器的參數類型;e)S爲C的成員方法的返回類型。
  每個已經裝載到JVM中的類都隱式含有裝載它的類裝載器的信息。類方法getClassLoader 可以得到裝載這個類的類裝載器。一個類裝載器認識的類包括它的父裝載器認識的類和它自己裝載的類,可見類裝載器認識的類是它自己裝載的類的超集。注意我們可以得到類裝載器的有關的信息,但是已經裝載到JVM中的類是不能更改它的類裝載器的。
  
  Java中的類的裝載過程也就是代理裝載的過程。比如:Web瀏覽器中的JVM需要裝載一個小應用程序TestApplet。JVM調用小應用程序裝載器ACL(Applet ClassLoader)來完成裝載。ACL首先請求它的父裝載器, 即系統裝載器裝載TestApplet是否裝載了這個類, 由於TestApplet不在系統裝載



虛擬機這個概念不好理解,什麼叫虛擬機?這和JAVA特有的可移植性有關,既然要可移動就不能和具體的計算機硬件有關,你這樣想吧,假如WINDOWS機器是美國人,UNIX機器是法國人,APPLE機器是德國人,並且假設你只會漢語而且你是這三個只懂本國語言人的老闆,你需要給他們佈置工作,於是你找到他們三個,面對面講話,他們誰也聽不懂啊,所以你們在一起工作沒有結果可言。所以你就想了個主意,你爲他們每個人都配了個形影不離的翻譯,你說話時對着翻譯,翻譯就把你的話解釋給他們三人,OK,問題解決,你應該會問這樣做不是要增加成本嗎,沒錯,翻譯的過程是要損失效率的,但這使你不至於什麼事都做不了。現在你應該能多少了解JVM的概念了吧,就象個翻譯。

這個將是很容易被忽視但是極端重要的東東—JAVA元素在內存中的分配情況。首先假定你有STACK堆棧的概念,它是內存中的一個部分,特性是數據訪問是先進後出,後進先出,這裏你要明白這8個字有個隱含的約束,就是時間,如果你要明確數據進出的順序,就要明確它們進出的時機,主要是出棧的時間,就是說你要明確指出什麼時候這個數據應該出棧,這樣才能保證先前進棧的數據有機會出棧。還有就是堆的概念,堆是程序運行時大量OBJECT對象存在的空間,你要有個形象的想象圖,不要以爲計算機是在神奇地憑空完成程序,就象自然萬物數據也是要有空間才能存在的,回顧剛纔說到的堆棧以及它的特性,先告訴你它被用來存放基本數據類型和REFERENCE引用,什麼是引用呢,書上說它是指向OBJECT物體的東西,用它來訪問具體的OBJECT,那爲什麼不直接訪問OBJECT呢?我猜想有如下原因(不一定是事實,也不是無道理),首先是因爲效率,訪問堆棧的速度要比堆快,因爲堆相對堆棧比較無序、無組織性,你也許要問,那爲什麼不把OBJECT對象放到堆棧裏呢,那樣不是更快麼?別忘了OBJECT和自然物體一樣也有屬性的,屬性不好理解,你就當它是體積、重量什麼的吧,把OBJECT放到堆棧是不可能的,堆棧容納不下(我猜測堆棧的大小是固定的值而且不會很大),即使能容納下個別的OBJECT,注意OBJECT的體積可不全一樣。明智的方法是用REFERENCE做個標示,假設你在一個阿拉伯國家當老師,那裏的人名字可是很長的,又假使你很熟悉你的每個學生所以不會認錯人並且你給他們每人一個固定的學號,上課時你要叫學生回答問題,你選擇叫他們完整的全名呢(阿拉伯國家叫全名以外的外號和縮短的名字是不尊重的行爲,也許吧,哈哈),還是叫他們的學號?REFERENCE引用就好比學號吧。另外還有STATIC靜態內存區,是用來專門保存靜態數據的,他們有着特殊的作用和意義,先不說這個。前面說到的這些還不完全,但有一點可以肯定,明確瞭解他們會使你更快地掌握編程語言,幫助理解和分析具體的程序,這些對學習任何一門語言來說都同樣重要。

現在來說一下OVERLOAD和OVERRIDE這兩個概念,不幸的是這兩個非常重要的概念被許多人錯誤地理解了,真是要細細說道一下。首先,看這兩個英文單詞:OVERLOAD—
超載,過載,重載,超出標準負荷;OVERRIDE:重置,覆蓋,使原來的失去效果。這兩個詞在外國人來看絕對不會弄混,可換了國人恐怕沒幾個初學者能搞得明白,問題在哪呢?我來告訴你,就在這個“重”字上,有許多人(包括我的大學C++課老師)讀OVERLOAD的中文譯名爲—重(chong 二聲,升調)載,實際上應該讀(zhong 四聲,降調),OVERRIDE的中文譯名是—重(chong 二聲,升調)置,把重載和重置混爲一體了,接着就有人把二者混用,原因主要還是對二者的涵義不明晰。先說OVERLOAD,其實OVERLOAD和OVERRIDE不是什麼具體的東西,二者都是機制,OVERLOAD我喜歡叫它超載,是對函數而言(如果不知道什麼是函數……那你不要看了)也就是方法(JAVA的函數標準稱謂),這裏引用一個經典的說明:你怎麼表達洗東西,是不是說洗手、洗車、洗衣服之類的?這些你平時再也熟悉不過的詞語的涵義是什麼你真的清楚嗎?比如洗車怎麼洗,洗手又怎麼洗,它們是相同的過程嗎?你肯用洗車的方式洗手?還是用洗衣服的方式去洗車?懂了嗎,儘管你說話的時候沒有明確地說我要洗我的手,用洗手的方式;或者我要用洗衣服的方式洗一件衣服。不需要那樣麻煩地表達,這就是超載的意思了,具體的語法用我解釋嗎?好吧,OVERLOAD超載是指在同一可訪問區內被聲名的、幾個、具有不同參數列的(參數類型/個數/順序,不同)、同名函數,程序會根據不同的參數列來確定需要調用的函數,這種機制叫超載,超載不關心函數的返回值類型(返回值類型及其爲何不能作爲超載函數的判斷因素在這裏不描述)。OVERLOAD覆蓋是指派生類(派生類這裏不做描述)中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體(花括號中的部分)不同,當派生類對象調用子類中該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋。明白了沒?這兩個看似生僻怪異的機制可是以後編程中經常用到的哦。

先寫到這裏了,提醒大家一下如果沒有英語基礎還是別學編程了,不然困難會很大,99%的技術文檔都是英文,MICROSOFT的MSDN應該有中文版本。另外90%的優秀書籍都是英文版本,比如BRUCE ECKEL的大作 “THINKING IN XXX”系列在他的網站直接提供電子版本的免費下載,很棒的書,目前免費電子版已經出到第3版。相信國內很多人都知道“侯捷”這個臺灣人,他曾翻譯過前面提到的B。E。大師的“THINKING IN JAVA Second Edition”,該書口碑極佳,書中對全數的名詞術語的翻譯比較精準,基本表達了原作者的意思,也是我看過的JAVA書中最爲出衆的一本,可惜世上無完美,由於侯先生是臺灣人,書中使用的詞語有的偏重臺灣化,儘管他已經在序中說明將盡量使用大陸的構詞習慣,有些地方還是差強人意。我在這裏強烈推薦中級和剛入門的朋友吃透這本書,書中的知識點闡述相當詳盡(個人認爲不完美,其實沒有什麼是完美的),函蓋了大部分重點要素,是一本系統學習JAVA的好書,但要注意,該書不是面向從零開始的讀者,使用者應該至少具有C或VB的語法概念,最好是懂點C,比如理解C中的數據類型概念、基本IF語句、變量聲明什麼的。在前面提到的TIJ 2nd版本中隨書帶有1CD,裏面包括了B。E。所著的“THINKING IN C --foundation for learning JAVA/C++”電子版,是學習JAVA的基礎。該書據說可在B。E。的網站找到,可能我笨,沒找到。在這裏希望大家多看英文原版書,大部分的優秀書籍都只有英文版可以看一下,因爲譯者的功力實在有限,有的譯本簡直慘不忍睹,真該扁那些出版社,沒本事學什麼人家出書嘛!另外,本土的作者水平普遍不高,而且書寫的比較沒有章法,個人感覺有的象無頭蒼蠅到處亂撞,細節交代不清,可能寫得連作者都不知其所云,就此收筆,主次輕重不分、不從讀者角度考慮、看了之後有十萬個爲什麼的想法;有的象蜻蜓點水,什麼都只觸及皮毛,當作簡介尚可,做教程看了也白看。大家也不要盲目地看英文書,外國也有不會念經的和尚,不要輕信書評和受歡迎度,我就看過幾個據說很有水平作者的拙作,很是糟糕,有的書甚至連基本原理都有錯誤,比如我曾親眼見過有本書中寫到:JAVA語言裏爲原始數據類型分配的內存空間隨具體實現的機器不同而不同。這樣的書看了無異飲鴆止渴呀!建議大致看一下全書,然後對比較熟悉的幾個地方仔細看一下,如果發現作者或譯者有明顯錯誤就放棄它。還有,如果一本書讀了大半也覺不出什麼新領悟的東西,就說明這本書內容和你掌握的重複,不要細讀了。好書真的會說話,每次看到一個新的亮點就是它在和你交談。至於那些高手嘛(再高的高手也有手軟的時候)光看技術文檔就可以了。這裏提醒大家技術文檔十分重要,一個不看技術文檔就寫代碼的程序員永遠也不是合格的程序員!希望大家多讀完整無錯的原代碼,對編程進步很有幫助,不要着急去調程序,沒會說全話就想唱歌,唱的出來麼?等到需要實際動手的時候就去做,那時侯你就知道自己並不是什麼都行的,實際情況和你計劃好的會有出入,是不是有的標準類函數記錯了呀,拿起書再看看吧。

發佈了75 篇原創文章 · 獲贊 0 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章