<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 188,comments - 176,trackbacks - 0

    第一部分 類和類的裝載

    我們來看一下類以及它們被JVM裝載的時候做了些什么?

    在這個新的有關動態的Java編程特征的系列文章中,將會看到在正在執行的Java應用程序的背后發生了些什么。企業級Java專家Dennis Sosnoski給出了Java二進制格式和發生在JVM內部的類中的事情。遵循這條路線,他介紹正在裝載的類所影響的范圍(從正在運行的一簡單的Java應用程序所必須的大量的類到在J2EE和類似的復雜的框架結構中類裝載器沖突所可能導致的問題)。

    這篇文章揭示了Java動態編程這組主題所包含的一系列的新的知識。這些主題包括從Java二進制類文件格式的結構到使用反射訪問運行時的元數據,以及所有的在運行時編輯和構造新的類的方法。貫穿這個材料的全部基本路線是Java平臺的編程思想,是比用其它直接編譯成本地代碼的語言更加動態的工作。如果你理解了這些動態的特征,你就可用Java語言做一些用其它的主流的編程語言所不能做的事情。

    在這篇文章中。我介紹了位于Java平臺的動態特征之下的一些基本概念。這些概念圍繞用于描述Java類的二進制格式,包括類被裝載進JVM(Java虛擬機)時所發生的事情。這篇文件不僅為理解這個系列主題的其它文章提供基礎,同時也演示了一些非常實際的在Java平臺上工作的開發人員所關心的事情。

    一個類的二進制形式
    用Java語言的開發人員通常不必關心通過編譯器運行他們的源代碼時所發生的一些細節問題。在這個系列主題中。我會介紹許多有關從源代碼到可執行的程序這個過程的背后細節,因此,我們先來看一下編譯器所產生的二進制類。

    二進制類的格式實際上是被JVM(Java虛擬機)規范定義的。正常的類的描述是一個編譯器利用Java語言的源代碼生成的,并且通常被保存在一以.class為擴展名的文件中。但是這些特征都不是本質的。其它的一些編程語言已經被開發使用Java的二進制類的格式,并且,因為一些目的,新的類的描述被創建并且被直接裝載進一個正在執行的JVM中。但是JVM所關心的,重要的不是這些源代碼或它是怎樣被存儲的,而是這個格式自身。

    因此,先來這種類格式看上去象什么呢?下面(List 1.)列出了一個非常短的類的源代碼,緊跟著是用編譯器輸出的這個類文件的一部分十六進制的顯示:
    List 1.Hello.java的源代碼和(部分)二進制表示
    public class Hello
    {
        public static void main(String[] args) {
            System.out.println("Hello, World!");
        }
    }

    0000: cafe babe 0000 002e 001a 0a00 0600 0c09  ................
    0010: 000d 000e 0800 0f0a 0010 0011 0700 1207  ................
    0020: 0013 0100 063c 696e 6974 3e01 0003 2829  ........()
    0030: 5601 0004 436f 6465 0100 046d 6169 6e01  V...Code...main.
    0040: 0016 285b 4c6a 6176 612f 6c61 6e67 2f53  ..([Ljava/lang/S
    0050: 7472 696e 673b 2956 0c00 0700 0807 0014  tring;)V........
    0060: 0c00 1500 1601 000d 4865 6c6c 6f2c 2057  ........Hello, W
    0070: 6f72 6c64 2107 0017 0c00 1800 1901 0005  orld!...........
    0080: 4865 6c6c 6f01 0010 6a61 7661 2f6c 616e  Hello...java/lan
    0090: 672f 4f62 6a65 6374 0100 106a 6176 612f  g/Object...java/
    00a0: 6c61 6e67 2f53 7973 7465 6d01 0003 6f75  lang/System...ou
    ...

    二進制的內部
    List1中所顯示的二進制類的表示的第一件事情是標識Java二進制類的格式的“café babe”簽名,這個簽名只是一種確認實際請求的Java類的格式的一個實例的數據塊的簡易方法。每個Java的二進制類,即使在不同的文件系統上,也需要用這四個字節開始。
    數據的其它部分不是很有趣。跟在簽名后面是一對類格式的版本號(在這個例子中,用1.4.1javac編譯生成的時候,會產生次版本為0、主版本為46------十六進制的形式是0x2e的版本號),然后是常量池中的條目的計數。跟在條目計數(在這個例子中是26,或0x001a)后面的是實際的常量池數據。這是保存所有類定義所使用的常量的地方。它包括類和方法的名字、簽名以及字符串(這些字符串是你能夠認可的在十六制的存放處的正確性的文本解釋)、以及連同在一起的各種二進制值。
    在常量池中項目是可變長度的,每個項目的第一個字節標識了項目的類型和它應該怎樣被解碼。我不打算對這些內容做詳細介紹,如果你有興趣以實際的JVM規范開始,這里有許多有用參考。關鍵點是常量池包含了所有的對其它類和這個類所使用的方法的引用,還有這個類自身以及它的方法的實際定義。盡管平均值可能會少一些,但是常量池的大小很容易的超過二進制類的在小的一半或更多。

    跟在常量池后面是幾個引用常量池條目的項目,它們是類本身,它的超類以及接口。這些項目的后面是有關字段和方法的信息,這些信息是做為復合結構來描述自己的。對于方法的可執行代碼以代碼屬性(code attributes)的形式被包含在方法的定義中。這種代碼是JVM的指令形式,通常叫做字節碼(bytecode),這是下一節的主題之一。

    在Java類的格式中屬性(Attributes)用來做為幾種定義的用途,包括已經提到的字節碼(bytecode),用于字段的常量值,異常處理,以及調試信息。但是,屬性(Attributes)不只有這些可能的用途。從一開始,JVM規范要求JVMs(Java虛擬機)忽略未知類型的屬性。這種要求對于屬性的使用提供了靈活性,使得它在將來能夠服務于其它的用途,例如提供與用戶類一起工作的框架所需要的元信息------這是一種Java源于C#語言所廣泛使用的方法。不幸的是,no hook have yet been provided for making of this flexibility at the user level.

    字節碼和堆棧
    組成類文件的可執行部分的字節碼是適應特定類型計算機(JVM)是的實際的機器碼,這所以叫做虛擬機是因為它是用軟件來設計實現的,而不是硬件。每個運行在JVM上的應用程序都是建立在這種機器的一種實現。

    虛擬機實際上相當的簡單,它使用堆棧結構,這就意味著它們在被使用之前指令操作要被裝載進一個內部的堆棧。指令集包括所有的一般的算術運算和邏輯操作,還有有條件和無條轉移,裝載/存儲,調用/返回,堆棧的維護,以及幾種特殊的指令類型。包括立即數的一些指令被直接編碼進指令,另外一些直接從常量池引用值。

    雖然虛擬機是簡單的,但執行起來卻不是這樣的,第一代JVM基本上是虛擬機的字節碼的解析器,相對而言,比較簡單,但卻遇到嚴重的性能問題———解析代碼總是要比執行本地代碼花費更長的時間。為了減少這些性能問題,第二代JVM添加了即時(JIT)翻譯。JIT技術是在Java字節碼第一次執行之前把它編譯成本地代碼,從而為重復執行提供了更好的性能。當前的JVM做的更好,它使用相應的技術來監控程序的執行并且選擇性使使用代碼得到優化。

    裝載類
    把源代碼編譯成本地代碼的語言(如C和C++)在源代碼被編譯之后通常需要鏈接這樣的步驟。這種鏈接過程把獨立編譯的源文件連同共享類庫的代碼合并到一起,從而形成一個可執行的程序。Java語言是不同的,使用Java語言,編譯器生成的類文件一般情況下單獨保存的,直到它們裝載進一個JVM為止,即使是建立一個JAR文件也不會改變這種情況———JAR文件只是類文件的一個容器。

    優于一個分開的步驟,JVM把類裝載進內存的時候,鏈接類成為JVM所要執行的工作的一部分。這樣就可以在初始化裝載的時候增加一些系統開銷,但是也為Java應用程序提供了高級的靈活性。例如,應用程序可以使用直到運行時才知道的實際實現的接口來編寫。這種后期綁定(late binding)的方法來裝配一個應用程序在Java平臺中被廣泛使用,servlets就是一個普通的例子。

    對于裝載類的規則在JVM規范的細節中被清楚的說明了。基本原則是類只有在需要的時候才被裝載(或者至少是顯示的裝載,JVM的這種方法在實際裝載過程中有一些靈活性,但是必需保持一個固定的類初始化的順序)。每個被裝載的類可以有其它的它所依賴的類,因此裝載過程是遞歸的。在Listing 2中的類顯示了這種遞歸裝載是怎樣工作的。這個Demo類包含了一個簡單的創建Greeter類的一個實例并且調用這個類的greet方法的main方法。Greeter類的構造器創建了一個Message的實例,然后它在greet方法中使用這個Message實例。

    Listing 2用于類裝載演示的源碼
    public class Demo
    {
        public static void main(String[] args) {
            System.out.println("**beginning execution**");
            Greeter greeter = new Greeter();
            System.out.println("**created Greeter**");
            greeter.greet();
        }
    }

    public class Greeter
    {
        private static Message s_message = new Message("Hello, World!");
        
        public void greet() {
            s_message.print(System.out);
        }
    }

    public class Message
    {
        private String m_text;
        
        public Message(String text) {
            m_text = text;
        }
        
        public void print(java.io.PrintStream ps) {
            ps.println(m_text);
        }
    }

    設置java命令的命令行參數為-verbose:class,這樣就可打印類裝載過程的軌跡。Listing 3顯示了使用這個參數的來運行Listing 2時的部分輸出:
    [Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
    [Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
    [Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
    [Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
    [Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    ...
    [Loaded java.security.Principal from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.security.cert.Certificate 
      from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded Demo]
    **beginning execution**
    [Loaded Greeter]
    [Loaded Message]
    **created Greeter**
    Hello, World!
    [Loaded java.util.HashMap$KeySet 
      from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
    [Loaded java.util.HashMap$KeyIterator 
      from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

    這里只列出了最重要的部分———全部的裝載軌跡由294行組成。試圖裝載Demo類所要觸發的需要裝載的初始的類的有279個(在這個例子中),這些是每個Java程序所使用的核心類,不管這個類的代碼如何的小,即使去掉Demo類main方法中的所有的代碼,也不會影響這個初始化的裝載順序。但是,裝載類的數量和名字與使用類庫的版本不同有關。

    在Demo類被裝載之后所列出的那一部分更加有趣。這里所顯示的順序是Greeter類只有這個類要被創建的時候才被裝載,但是Greeter類使用了Message類的一個靜態實例,所以,在創建一個Greeter類的實例之前,后面的Message類也需要被裝載。

    當一個Java類被裝載和初始化時。在JVM內部發生很事情,包括對二進制類格式進行解碼,檢查與其它類的兼容性,確認字節碼操作的順序,并最終創建一個java.lang.class的實例來描述這個新類。這個類對象成為JVM創建的這個新類的所有實例的基礎。同時它也標識了被裝載類的自身———你可以在一個JVM中裝載同樣的二進制類的多個副本,每個都擁有它們本身的類的實例。盡管這些副本共享著同樣的類名,但是對于JVM來說他們獨立的類。

    Off the beaten (class) path

    裝載進JVM中的類是通過類裝載器來控制的,有一個bootstrap類裝載器建立在JVM的內部,它負責裝載基本的Java類庫中的類。這個特殊的類裝載器有一些專用的特征。首先,它只基于根類路徑來裝載類。因為這些是系統類所信賴的,bootstap裝載器跳過那些確認為不信賴的類。
    Bootstrap不僅是一個類裝載器。.對于一個起動器,JVM為了從標準的Java擴展API中裝載類定義了一個擴展的類裝載器,并且為了從一般的類路徑中(包括應用程序的類)裝載類還定義了一個系統類裝載器。應用程序為了特殊的目的也可定義它們自己的類裝載器(例如運行時類的重載)。這種被添加的類裝載器繼承于java.lang.ClassLoader類(也可能是間接的),這種方式為用一個字節數組來建立一個內部的類描述(一個java.lang.Class的實例)提供了核心的支持。每人被創建的類都能夠做到被裝載它的類裝載器所感知擁有。類裝載器通常保持一個它們所裝載的類的映射,如果這些類被再次請求,通過名字就能夠找到它。
    每個類裝載器也保持著一個對父類類裝載器的引用,以bootstrap裝載器為根定義了類裝載器的樹。當一個特殊的類的實例被請求時(通過定義名字來請求),最初,無論哪一個類裝載器處理這個請求,通常情況下,在首次嘗試直接裝載這個類之前,都要與它們的父類裝載器進行協商。如果有多層類裝載器,就要這樣遞歸申請,這就意味著一個類在裝載它的類裝載器中將是不可見的,而且對它所有的子類裝載器也是不可見的。它也意味著如果在一個鏈中的一個類能夠被多個類裝載器裝載,那么距這個類裝載的樹最上層的那個類才是實際裝載這個類的類裝載器。

    有許多多個應用程序類裝載器被Java程序使用的情況,一個例子就是在J2EE框架內,每個能框架來裝載的應用程序,都需要一個獨立的類裝載器來防止應用程序間類的干擾。框架代碼自身也會使用一個或多其它的類裝載器,來防止應用程序間的沖突。一套完整的類裝載器組成了一個樹形結構的層次以便在不同的層次上裝載不同類型的類。

    裝載器的樹形結構
    做為一個類裝載器層次描述的例子,Figure 1顯示了Tomcat的servlet引擎所定義的類裝載器的層次結構。Common類裝載器從Tomcat的安裝目錄中的JAR文件中裝載那些打算在服務器和Web應用程序之間共享的代碼。Catalina裝載器是Tomcat自已的類,Share裝載器用于Web應用間共享的類。最后,每個Web應用程序都有它們自己的裝載器做為它們的私有類。
    Figure 1.Tomcat 類裝載器
     
    在這種環境類型下,保存所使用的正確的裝載器的軌跡對于正在請求的一個新類來說可能是雜亂無章的。因為這樣,所以在Java2平臺中的java.lang.Thread類添加了setContextClassLoader和getContextClassLoader方法,這些方法讓框架可以為每個應用程序在運行來自應用程序的代碼時設置所使用的類裝載器。

    能夠裝載獨立的類的集合的靈活性是Java平臺的一個重要特征。盡管這個特征是有益的,但它卻能在一些實例中產生混亂。其中之一就是連續的處理JVM的類路徑的問題。例如,在Figure 1中所顯示的Tomcat中的類裝載器的層次關系中,被Common類裝載器所裝載的類將不能夠直接被Web application裝載的類來訪問(通過名字)。把這兩個類裝載器結合到一起的僅的方法是通過使用接口使用雙方的類的集合彼此可見,在這人案例中,通過java servlets實現的java.servlet.Servlet類包含了這種方法。

    當因為一些原因在類裝載器之間移動代碼時,就可能產生一些問題,例如,當J2SE1.4把處理XML的JAXP API    移到標準的發布版中時,對于那些先前的信賴它們自己XML API的實現應用程序來說,就會產生一些問題。在使用J2SE1.3的情況下,能夠通過在用戶的類路徑中包含相應的JAR文件就能訪問自己的API,但在J2SE1.4中,當前的APIs的標準版中這個裝載器是在擴展類路徑中,因些一般情況下,它將會不管出現在用戶類路徑的任何實現。

    當使用多個類裝載器時,也可能有其它類型的混亂。Figure 2顯示了一個identity crisis類在一個接口和相關聯的實現獨立的類裝載器分別裝載時所出現的結果。盡管接口和類的名字以及接口是相同的,但是,但是來自于一個裝載器的類的實例卻不能被來自另一個裝載器的正在執行的接口所承認。這種混亂能夠通過(如Figure 2中所示)把類I的接口移入System類的裝載器空間來解決,雖然這樣依然有兩個獨立的類A的實例,但是它們都實現相同的接口I。

    Figure 2類identity crisis
     

    Java類定義和JVM規范一起為運行時的代碼匯編定義了一個非常強大的框架。通過使用類裝載器Java應用程序能夠和類的多個版本一起工作,否則就會產生沖突。類裝載器的這種靈活性甚至充許在一個應用程序連續執行的時候動態的重新裝載被編輯的代碼。
    Java平臺在這方面的靈活所付出的代價是在啟動應用程序時要付出更的系統開銷。在應用程序(即使是最小的應用程序代碼)開始執行之前,幾百個獨立的類需要JVM來裝載。一般情況下,這種起動成本使得Java平臺更加適應長時間運行的服務器類型的應用程序,而不適應于那些經常使用的小程序。服務器應用程序也從運行時的代碼匯編的靈活性中獲取最大的好處,因此,Java平臺成為日益流行的開發平臺是不足為奇的。

    在這個系列的第2部分中,將包含一個使用Java平臺的動態機制基礎的另一個特點的介紹:這就是Reflection API。Reflection讓你的正在執行的代碼訪問內部的類的信息。這種機制是創建靈活的代碼的一個強大的工具,它能夠在運行時沒有必需的任何連接類之間的源代碼的前提下把這些代碼鏈接在一起。但是做是最有價值的工具,你需要知道什么時候和怎樣來使用以獲取最大的收益。

    http://www.csai.cn/

    posted on 2007-05-24 11:02 cheng 閱讀(197) 評論(0)  編輯  收藏 所屬分類: JBS
    主站蜘蛛池模板: 狠狠亚洲狠狠欧洲2019| 亚洲精品成人网站在线播放| 免费黄网站在线看| 亚洲成aⅴ人片在线观| 日本免费一区尤物| 很黄很污的网站免费| 亚洲一线产区二线产区区| 亚洲午夜福利精品无码| 国产高清不卡免费在线| eeuss影院免费92242部| 亚洲国产精品久久久久秋霞影院| 免费在线黄色网址| 精品无码AV无码免费专区| 粉色视频成年免费人15次| 久久久久亚洲精品天堂| 亚洲熟女乱综合一区二区| 国产无人区码卡二卡三卡免费 | 国产免费AV片在线播放唯爱网| 高潮内射免费看片| 亚洲毛片免费视频| 亚洲无线码在线一区观看| free哆啪啪免费永久| 精品97国产免费人成视频 | 特级aaaaaaaaa毛片免费视频| 亚洲电影国产一区| 免费播放春色aⅴ视频| 久久精品免费全国观看国产| 成人片黄网站色大片免费观看cn | 亚洲精品国产精品乱码不卡√| 国产在线a免费观看| 久久久久久AV无码免费网站下载| 美女黄网站人色视频免费| 亚洲最新在线视频| 亚洲ⅴ国产v天堂a无码二区| 亚洲精品国产高清嫩草影院| 成人人免费夜夜视频观看| 99视频在线精品免费| 中文字字幕在线高清免费电影| 国产成人亚洲精品91专区高清 | caoporn成人免费公开| 亚洲欧美黑人猛交群|