天天看點

Jikes 研究虛拟機(RVM)五 結論

<a>相關工作</a>

HotSpot 最初解釋位元組碼,編譯(并内聯)被頻繁調用的方法。Jalape�o 的快速編譯器将扮演與 HotSpot 的解釋器相似的角色。其它所有都是同等的,這對 HotSpot 會帶來啟動方面的好處,對 Jalape�o 會帶來性能方面的好處。我們不期望哪個好處會特别大,但這仍然可以看到。如果未優化的 Jalape�o 代碼比解釋後的 HotSpot 代碼表現更好,這将允許 Jalape�o 優化編譯器把更多的資源集中在它優化的代碼上。用 Java 代碼實作 Jalape�o 允許優化編譯器内聯和優化頻繁被調用的運作時服務,HotSpot 通過調用本機代碼(極好地優化了的 C 例程)通路這些服務。

HotSpot 把 Java 線程作為宿主作業系統線程實作。這些線程是完全搶先的。Jalape�o 排程它自己的準搶先線程。我們希望這将允許支援更多的線程、更輕量的同步和使從正常操作到垃圾回收的更平滑切換(特别是在有極多線程的情況下)。HotSpot 的每線程方法激活堆棧符合宿主作業系統的調用約定。這應會給 Jalape�o 較小的空間和性能方面的好處(盡管 Jalape�oill 在它 确實調用 C 代碼時獲得了性能方面的好處)。

我們沒有關于 HotSpot 的鎖定機制的資訊。

<a>結論</a>

用于 Java 伺服器的 Jalape�o 的虛拟機是用 Java 語言編寫的。傳統上用本機方法支援的運作時服務主要地用 Java 代碼實作。

Jalape�o 的對象布局支援單指令字段通路,對數組元素的三指令通路,硬體空指針檢查和四指令虛方法排程。通過全局 JTOC 數組快速通路靜态字段和方法也實作了。

Jalape�o 的線程通過虛拟處理器進行多路複用。線程切換是準搶先的。三個不同的鎖定機制提供了輕量級同步,而不用作業系統支援。

Jalape�o 的記憶體管理子系統支援一系列記憶體管理器,每個記憶體管理器由一個并發對象配置設定器和一個并行的、類型确切的、停住一切的垃圾回收器組成。繁衍的和不繁衍的,拷貝的和非拷貝的管理器都被支援。增量及并發回收器正在研究之中。

Jalape�o 的三個可互操作的編譯器提供了不同級别的動态優化,確定了實時的線程搶先,并且生成了支援異常處理、堆棧中的調用的位置和調試的表。

Jalape�o 的優化編譯器為已被識别為頻繁執行或計算密集的方法産生優質代碼。需被再次編譯的方法将根據運作時配置動态地選擇。

我們已經證明了用 Java 語言為 Java 伺服器建構虛拟機的可行性。我們尚未證明這樣一個虛拟機能達到并保持世界級的性能。我們正在為此努力。

<a>附錄 A:MAGIC</a>

為配置設定一個對象,Jalape�o 的記憶體管理器必須通路原始記憶體以獲得要求大小的一塊可用空間。它們“周遊”線程堆棧以識别堆棧幀中的對象引用。拷貝管理器在垃圾回收期間通路對象頭以标記對象,通路原始記憶體以拷貝一個對象。異常處理要求非結構化的傳送控制以轉到适當的 catch塊(“go to”在 Java 語言中是禁止的)。靜态資料和方法通過一個專用的機器寄存器通路,不能從 Java 指令通路這個專用寄存器。輸入和輸出要求通路 Java 語言不知道的作業系統服務。線程切換依賴于接收到來自作業系統的周期性中斷。Jalape�o 的鎖定機制是用 PowerPC 指令實作的,這些指令無法表達成 Java 位元組碼。不打破 Java 的程式設計模型,這些操作都無法進行。

為實作 Jalape�o Java 代碼,有必要增加 Java 的功能,以包含本機方法傳統上要求的功能:

調用作業系統服務

使用特定于體系結構的機器指令

通路機器寄存器和記憶體

強制對象引用原始位址, 反之亦然

把執行轉到任意位址

Jalape�o 必須要有這些功能,但 Jalape�o 也必須防止使用者應用程式能使用這些功能。

在專門的 MAGIC 類的幫助下,Jalape�o 的編譯器支援這些違例。這個類的方法符合 Java 外部操作,Jalape�o 必須能夠執行這些操作。這些方法的體是空的。Java 的源代碼編譯器能夠編譯它們。但是,Jalape�o 的編譯器忽略這些結果位元組碼。而且,這些編譯器識别出 MAGICR 類的名字并内聯地插入必需的機器代碼。為确認使用者代碼未違背 Java 的限制,當 Jalape�o 的編譯器碰到調用一個 MAGIC 方法時,它們将驗證正在編譯的方法是 JVM 的一個已授權的部分。

需要使用 MAGIC 類的代碼在這樣做時需特别小心。将要講述的規則就是一個原因。某些操作要格外小心。涉及原始位址的計算尤其微妙。MAGIC 方法 objectAsAddress 把對象引用轉換成原始位址(一個整數)。例如,在進行動态連結時就需要這個功能。然而,它也是有問題的。Jalape�o 的拷貝記憶體管理器在移動所引用的對象時會更新對象引用,但原始位址卻未被更新。在進行涉及原始位址的計算時,為避免垃圾回收的發生,必須很小心,以免拷貝回收器使這些位址無效。這通過調用一個能禁用垃圾回收的方法來避免。

已經禁用了垃圾回收的線程不能試圖建立一個對象,因為如果記憶體不足,系統将會挂起。(注意,其它線程可以自由申請記憶體。如果無法得到記憶體,則這些線程被延遲,而且一旦垃圾回收被重新啟用,就将開始進行一個回收。)這個限制有一些微妙的牽連。類不能被裝入,因為對象是在類裝入期間建立的。這意味着必須避免動态連結。類型的強制轉型(和存儲到對象數組)也不允許,因為這可能也要求類裝入。類似地,如果線程試圖進入一個共享對象的一個管程,而這個管程目前正被一個正在等待垃圾回收的線程占有,那麼系統将陷入死鎖。是以,線程在進行涉及原始位址的計算時,必須嚴格限制在 Java 功能的子集内。

就線程當它的垃圾回收被禁用時進行讓出(顯式地或隐式地)來說,也會有點問題。這樣一個讓出可能會任意地延遲所需的垃圾回收。當線程的垃圾回收被禁用時,隐式線程切換被延遲(而顯式線程切換被禁止)。

Jalape�o 系統中大約有 650 個 Java 類,其中大約有 110 個通路了 MAGIC 類。其中隻有 12 個類要求禁用垃圾回收。

<a>附錄 B:開始</a>

一組相當堅實的服務 ― 一個類裝入器,一個對象配置設定器,一個編譯器 ― 在 JVM 能夠裝入正常操作所需的所有剩餘服務之前就必須已經存在。用本機代碼編寫的 JVM 的初始服務,或者運作在另一台 JVM 上的 JVM,都從底層運作時例程可用。Jalape�o 不是用本機代碼編寫的,它沒有底層運作時例程。是以,我們把基本的核心服務裝配進一個可執行 引導映象,這個引導映象先于 JVM 運作。這個引導映象是 Jalape�o 虛拟機的一個快照,它被寫入到一個檔案中。随後,這個檔案被裝入記憶體并執行。

引導映象由一個名為 引導映象編寫器(boot-image writer)的 Java 程式建立。引導映象編寫器構造運作中的 Jalape�o 虛拟機的實體模型(mock-up),然後把它包裝進引導映象。引導映象編寫器是一個普通的 Java 程式,它可以在任何 JVM 上運作。運作引導映象編寫器的 JVM 将被稱為 源 JVM,而産生的結果 Jalape�o 虛拟機則稱為 目标JVM。

引導映象編寫器類似于一個交叉編譯器和連結器:它把位元組碼編譯成機器碼并重寫機器位址以把程式元件綁定進可運作映象。然而,由于 Jalape�o 的編譯器、類裝入器和運作時資料結構都是 Java 代碼形式,而不似多數編譯器,是以引導映象編寫器也必須把“活動”對象綁定進引導映象。

引導映象編寫器在源 JVM 中執行個體化 Java 對象,這些對象表示了目标 JVM。然後,它使用 Java 内置的反射功能把這些實體模型對象從源 JVM 的對象模型轉換為 Jalape�o 的對象模型。引導映象編寫器和這種自引用特征使得它相對簡單 ― 它實際上隻是一個對象模型轉換器。

由于 Jalape�o 是一個 Java 程式,是以它的每個元件都是一個 Java 對象,而且,通過在 Jalape�o 的每個主子系統中執行專門的初始化方法,引導映象編寫器能夠構造其實體模型。定制的類裝入器確定了執行這些代碼所需的任何類都既裝入到了源 JVM 中,也裝入到了實體模型中。當裝入一個類時,類的方法被編譯(由運作在源 JVM 中的 Jalape�o 的編譯器執行)并被包含進實體模型。

要成功實施把類同時裝入源 JVM 和它的目标 JVM 的實體模型的政策,就需要一個完整的類清單。如果當 Jalape�o 開始運作時,核心運作時環境的一個方法引用了不在引導映象中的任何類,則将産生無窮遞歸的結果:運作時環境要求裝入它自己的一部分以裝入它自己的一部分 ― 等等。

通過仔細的計劃和反複試驗,我們解決了判斷實體模型中最少需要哪些類以阻止這種情形的問題。Jalape�o 的所有核心類都以 VM 為字首指令。這裡是提供使虛拟機能執行編譯、記憶體管理和動态類裝入的充足機器所需的類。這個專門的字首由 Jalape�o 的編譯器識别并用于抑制正常的動态連結規則:編譯器從不在有這個字首的類的方法之間生成動态連結代碼。小心地編寫核心類以避免對 Java 庫類的不必要使用。這些基礎類 ― java.lang.Object、java.lang.Class、java.lang.String 和一些 I/O 類 ― 是絕不可以排除在外的。VM_ 類和這些基礎 Java 類構成了我們認為有必要在引導映象中出現的類的啟動集。

然後通過反複試驗識别出另外一小部分依賴(例如,Integer、Float、Double 和各種數組和異常類)。我們編譯一個引導映象并試圖執行它。如果它在試圖(遞歸地)裝入類 X時崩潰了,那我們就把 X 添加到寫入引導映象的類的清單,并反複進行這個過程。這個過程集中進行了少數重試,也不證明一旦 VM_ 類的實作隐定了,這會成為一個維護問題。

實體模型在完成之後被轉換成引導映象。這包括查找實體模型中的所有對象,把它們轉換成 Jalape�o 的對象格式并存儲進 引導映象數組。運作中的 Jalape�o 虛拟機的所有元件都可以從一個 JTOC 數組中獲得(請參閱靜态字段和方法部分)。在實體模型中,JTOC 被編碼成三個并行的數組:一個整數數組(用于原始值),一個對象執行個體數組(用于引用)和一個用于區分這兩個數組的布爾數組。JTOC 數組的結構被遞歸地周遊,所碰到的值(引用的和原始的)被轉換到引導映象數組。由于每個裝入類的類型資訊塊(請參閱對象頭部分)都從 JTOC 引用,是以所有必需的編譯後的方法體都将被包含到引用映象中。

這個轉換過程使用了映象。引導映象為實體模型中的每個對象獲得 java.lang.Class 對象并在由 getFields 方法傳回的字段上反複進行一些操作。對每一個字段,它從源對象抽取字段值,從對象的 Jalape�o 類描述中抽取目标字段偏移量。然後,它把位于從對象的索引偏移該偏移量的值寫入引導映象。當碰到對象引用時,我們不能使用來自實體模型的任何值。通過用一個作為配置設定到的引導映象維護的散清單,實體模型中的引用被轉換成引導映象位址。(包含引導映象中的所有引用的位址的數組可以被包含進引導映象,以支援引導映象的重定位。)

總的來說,引導映象編寫器一個字段一個字段地拷貝 Java 對象,從實體模型到引導映象,同時從源 JVM 的對象模型轉換到目标 JVM 的對象模型。有賴于 Java 的映象功能,我們碰上了一個麻煩:Sun 的 Java 開發包(Java Development Kit),版本 1.1.4 不允許對私有字段的反射通路。這在 Java 2 軟體開發包(Java 2 Software Development Kit)中不是一個問題,這個開發包允許這樣的通路。通過預處理類檔案,關掉私有位,我們在更早的版本中就解決了這個問題。

除了能從 JTOC 數組中通路的對象外,引導映象中還需要另外兩個對象:一個初始線程對象和一個“引導記錄”。初始線程對象包含一個空堆棧,它已為在 Jalape�o 啟動時運作 boot( ) 方法的第一條指令準備就緒;“引導記錄”是引導映象和引導映象運作器(稍後論述)之間的接口。這個引導記錄包含映象中的開始、結束和最後使用的位址,也包含用于啟動 Jalape�o 的四個寄存器值,boot( ) 方法的位址和 AIX 系統調用的位址。當這些值被存儲到引導映象數組時,這個引導記錄被寫到磁盤上。

一個稱為 引導映象運作器的短小程式啟動 Jalape�o 的運作。它把引導映象讀進記憶體,把四個寄存器設定為指定值并轉到 boot( ) 方法分支。引導映象是用 C(帶有一些彙程式設計式以設定寄存器和執行最後的分支)寫的,不是 Java 代碼,是以 它不需要在 JVM 上運作。

當 boot( ) 方法開始執行時,虛拟機處在一個脆弱狀态:它能夠運作機器指令的單個線程,但它還未建立支援它自己的執行所需的外部作業系統資源。引導映象無法建立這些作業系統資源,因為這些資源要引用外部狀态,而這些狀态将不存在,一直到引導映象執行。是以,Jalape�o 必須執行另外的初始化。

在引導期間,虛拟機初始化特定于硬體的位址(例如,它将最終在自己的堆棧上建立硬體監視頁),打開與 Java 庫的 System.in、System.out 和 System.error 流對象相對應的檔案,分析指令行參數并建立與一個與目前執行環境相對應的 System.Properties 對象。然後,通過建立充當虛拟處理器的作業系統線程初始化多線程子系統,Java 線程在虛拟處理器上多路複用。最後,啟用定時器中斷以支援線程搶先,生成一個 Java 線程以運作指令行指定的應用程式。

Jalape�o 運作直到最後一個(非守護)Java 線程終止或調用了 System.exit( )。

<a>參考資料</a>

<a>作者簡介</a>

B. Alpern has co-authored this article

  C. R. Attanasio has co-authored this article

  J. J. Barton has co-authored this article

  M. G. Burke has co-authored this article

  P. Cheng has co-authored this article

  J.-D. Choi has co-authored this article

  A. Cocchi has co-authored this article

  S. J. Fink has co-authored this article

  D. Grove has co-authored this article

  M. Hind has co-authored this article

  S. F. Hummel has co-authored this article

  D. Lieber has co-authored this article

  V. Litvinov has co-authored this article

  M. F. Mergen has co-authored this article

  T. Ngo has co-authored this article

  J. R. Russell has co-authored this article

  V. Sarkar has co-authored this article

  M. J. Serrano has co-authored this article

  J. C. Shepherd has co-authored this article

  S. E. Smith has co-authored this article

  V. C. Sreedhar has co-authored this article

  H. Srinivasan has co-authored this article

  J. Whaley has co-authored this article