針對 Java 平台 5.0 版本的 IBM Developer Kit 標志著顯著的進步,它在語言特性和底層執行技術方面有重大改進。本文是一個分 5 部分的文章系列的第一篇,概述了 IBM 對它的虛擬機技術所做的一些主要改變和改進,包括傳統的垃圾收集、共享類數據,以及在監視和調試工具及 API 方面的改進。但是,在討論 IBM 實現的改進之前,我們先看看 Java 5.0 本身的改進。
Java 5.0 的改進
自從引入 Java 2 平台以來,Java 2 Standard Edition(J2SE 5.0)在 Java Class Library(JCL)API 和 Java 虛擬機(Java Virtual Machine,JVM)規范中引入了許多特性改進。這些特性在所有 Java 技術實現廠商的所有 5.0 實現中都可用了。它們主要涉及兩個領域:開發的簡化以及監視和管理。
簡化開發的特性
5.0 版本中的簡化開發特性的設計目的是,讓開發人員能夠用更少的代碼建立簡單構造,以及提供更多的編譯時檢查,從而幫助開發人員在開發周期中更早地發現問題。下面是對這些特性的簡要介紹:
用泛型提供編譯時類型安全性:泛型與 C++ 模板相似。一般的(即泛型(generic))類獨立於具體的類型,在實例化時通過使用參數化類型(parameterized type)提供類型安全性。結合使用參數化類型和泛型類就可以進行編譯時類型安全性檢查,Java 5.0 平台中的集合類使用了這種方法。
擴展的 for 循環:這個新的語言構造與其他語言中的 for each 循環相似,它簡化了循環遍歷集合和數組的過程,因為不再需要使用顯式定義的迭代器和索引變量。
原生類型的自動裝箱:這個特性簡化了將原生類型插入集合對象的過程,因為不再需要將 Java 原生類型(比如 int)裝箱(box) 成對應的包裝器類(比如 java.lang.Integer),在刪除它們時也不需要開箱(unbox)。
類型安全的枚舉:這個特性引入了 Java 語言對枚舉類型的支持,提供了比使用靜態 final 聲明更強大且類型安全的解決方案。
支持導入常量:這個特性使靜態方法和字段能夠被導入,這樣在訪問靜態成員時就不必使用完全限定的類名。
Java Language Metadata(標注):這個特性允許開發人員將標注(annotation) 添加到代碼中。標注作為修飾符,可以添加到包、類、接口、方法或字段聲明中。此信息存儲在源代碼文件和類文件中,工具和 Java 應用程序可以通過 Java Reflection API 獲得它。用於文檔編制、編譯器檢查和代碼分析的工具可以使用這些額外信息。
並發工具:這個特性為開發並發類提供了基本構造塊,包括線程池和線程安全的集合,並引入了低級鎖定原語,包括信號量和原子性變量。
監視和管理特性
J2SE 新的監視和管理特性的設計目的是簡化對 Java 運行時的狀態的監視。可以使用監視和管理 API 從 Java 代碼調用這些功能,或者使用 JVM Tools Interface(JVMTI)從 C 代碼調用:
監視和管理 API:這個特性使 Java 程序或遠程代理能夠監視虛擬機的 “健康狀態” 並觀察其他系統級的活動和事件。可以利用這些特性開發自治和自適應系統。
JVM Tools Interface:JVMTI 是一種更輕量的、靈活的 JVM Profiling Interface(JVMPI)替代品,它是一個基於 C 的接口,用於編寫開發時和運行時監視工具。
來自 IBM 的增值改進:概述
通過 Java 編譯器、JCL API 和 JVM 規范在 5.0 中添加的規范和 API 改進影響了 Java 平台的所有新實現;另外,允許 Java 廠商在自己的 Java 實現中開發和提供自己的增值改進。IBM 以兩種形式提供自己的改進:IBM 開發的 Java 語言擴展和 Java 運行時環境的 IBM 實現中的改進。
Java 語言擴展
IBM 的增值 Java 語言擴展包括三個主要組件:對象請求代理(object request broker,ORB)、XML 和 安全性。這三個組件是 IBM 提供的代碼,提供了顧客和某些 IBM 產品需要的特性:
ORB:IBM 發起了 RMI-IIOP 的開發並將其包含到 J2SE 中,作為 RMI-JRMP 的替代品。在此之後,IBM 繼續開發自己的實現,來滿足顧客需求並確保版本之間的互操作性,尤其是對於 WebSphere Application Server。
XML 和 XSLT:IBM 基於 Apache Xerces Java 和 Xalan Java 開放源碼項目(IBM 是這個項目的主要捐獻者)開發了一個 XML/XLST 規范實現。IBM 包包含在 Java 5.0 規范中沒有指定的 XML API,以及 Xerces Native Interface 和 XML Schema API。
安全性:IBM 通過標准 Java API 提供了廣泛的安全服務。安全性組件包含各種安全性算法和機制的 IBM 實現。除了基本的安全性提供者之外,IBM 還為密碼術硬件提供了 FIPS 支持。另外,提供了 iKeyman 實用程序來管理密鑰和證書。
Java 運行時改進
Java 運行時的 IBM 實現針對 5.0 版進行了大量開發工作,影響到所有三個主要運行時組件:虛擬機(virtual machine,VM)、垃圾收集器(garbage collector,GC)和即時(just-in-time,JIT)編譯器。這些努力有兩個主要目的:提高應用程序的執行性能,以及提高可靠性、可用性和可伸縮性(RAS)。
實現這些改進的方式有兩種:轉移到所有 Java 運行時風格(Micro Edition(ME)、Standard Edition(SE)和 Enterprise Edition(EE))的通用代碼基,IBM 為這些風格產生了端口;在這三個組件中分別引入改進。
在本文的其余部分和本系列的後續文章中,我們將詳細討論所有與 Java 運行時相關的改變。
通用代碼基
IBM 長期以來一直為 Java 平台的所有三個版本開發實現。自 J2SE 5.0 發布以來,Java 運行時的 IBM 實現的所有底層組件都是在一個通用代碼基上構建的。
通用代碼基是使用一個框架引擎系統和可插入配置構造的,這可以支持最大程度的代碼共享,同時可以迎合每個 Java 版本需要的任何功能差異。這改進了 J2SE IBM 實現的內存占用量、啟動時間和性能,並改進了 J2ME IBM 實現的可伸縮性和可服務性。由於對相同的通用代碼進行了廣泛深入的測試,各個版本都由此受益了,因此改進了可靠性和穩定性。
Java 堆棧和線程寄存器中的值
Java 堆棧中的值是相對的,因為堆棧上的幀包含 Java 方法的局部變量。線程寄存器中的值也是相對的,因為這裡存儲當前正在執行的方法的局部變量和操作數。
垃圾收集器改進
除了利用可插入配置轉移到通用的垃圾收集框架之外,對於 GC 組件還有 4 項主要改進:從保守性收集器轉移到類型精確的收集器,引入了一個並行收集器,引入了分代的並發收集器,重新設計了詳細 GC 日志記錄設施。
類型精確的收集器
以前的 J2SE IBM 實現實現包含一個保守性(conservative) 垃圾收集器。這種收集器假設線程的 Java 堆棧或線程寄存器中的每個值都可能包含對 Java 對象的引用,因此稱為保守性的。
這意味著垃圾收集器必須追蹤每個值,並判斷它是否指向 Java 堆上的對象,這可能導致將未被引用的對象標為被引用的 —— Java 堆棧或寄存器中的值可能實際上只是一個 long 值,但是這個值碰巧指向一個有對象存在的位置。在這種情況下,就會發生所謂的保留垃圾(retained garbage) 的情況,因為實際上已經不再需要的對象卻在垃圾收集過程中保留下來了。這會使應用程序占用的內存比真正需要的內存量大。
將對象分配到碎片化的堆中
Java 對象要求創建一個單一的連續的內存區。如果 Java 堆有殘留的碎片,那麼可能沒有足夠的連續內存可以分配給給定對象,即使總內存是足夠的。即使在內存看起來足夠的情況下,這也會導致 OutOfMemoryError。
使用保守性收集器的另一個影響是需要對 Java 對象進行 pin 和 dose。當從 JNI(本機)代碼引用對象時,它們就被 pin。當從 Java 堆棧或寄存器引用對象(有意或無意地成為保留的垃圾)時,對象就成為 dose 的。在垃圾收集器對 Java 對象進行緊湊排列期間,pin 和 dose 會阻止對象在 Java 堆上移動。這是因為在對象移動時,Java 堆棧和寄存器中的對象引用無法更新為對象的新位置;對於保留的垃圾,它實際上會變成一個不引用 Java 對象的值。
在垃圾收集的緊湊排列階段,無法移動這些對象,這造成無法完全消除 Java 堆上對象的碎片化,因此留下了殘留的碎片。這個問題造成在 Java 堆上即使看起來有足夠的空閒內存,也無法分配對象,同時導致運行應用程序所需的 Java 堆比真正需要的大。
這兩個問題都通過轉移到類型精確的收集器(type-accurate collector) 解決了,這種收集器維護自己的經過良好描述的類型精確的堆棧。這避免了保留垃圾的問題,因為它知道一個值是否引用 Java 對象。因為能夠編輯這些類型精確的堆棧,所以在緊湊排列期間對象可以移動了,因而消除了殘留碎片問題。
並行收集器
緊湊排列是運行垃圾收集時最消耗時間的階段。為了減少緊湊排列的時間,在 Java 5.0 技術的 IBM 實現中引入了並行緊湊排列(parallel compaction)。
並行緊湊排列允許多個線程幫助在 Java 堆上移動對象,從而將大量的小塊空閒空間合並成少量的大塊空間。實現的方法是,對於進程可以使用的每個 CPU,建立一個線程,並將 Java 堆分割成許多名義上的區域,這些線程分別負責每個區域的緊湊排列。
分代的並發收集器
分代並發(或稱為 gencon)收集器利用了 “大多數對象在年輕時就死了” 這一並不可靠的假設,通過創建分成兩代的 Java 堆來實現。通過將對象分成新老兩代,收集過程可以主要關注年輕的對象。年輕(也稱為嬰兒(nursery)) 代使用一個半空間復制收集器,而老年(也稱為長存的(tenured)) 代使用並發的標志掃描收集器。
消息 GC 日志記錄更新
為了改進 GC 組件的 RAS 品質,GC 日志記錄機制在兩個方面進行了更新。日志記錄器提供更詳細的信息並采用 XML 格式而不是一般文本;由於第一次數據捕捉失敗,所以它還將此數據的子集記錄進內存中的緩沖區。
詳細 GC 輸出轉移到基於 XML 的結構,因此更容易通過簡單的 XML 讀取器(比如 Web 浏覽器)查看數據,也更容易通過各種詳細 GC 分析工具進行分析。
內存中的跟蹤緩沖區在發生失敗時或者應用戶的請求存儲到文件,所以即使詳細 GC 沒有啟用,它也可以提供關於 Java 內存使用情況和 GC 狀態的基本信息,而且它大大提高了第一次失敗數據捕捉信息的質量。
對 JIT 編譯器的改進
盡管 Java 平台的用戶對 JIT 編譯器的內部不了解,但是它對 Java 運行時的應用程序執行性能影響很大。它在運行時將 Java 字節碼轉換為優化的機器碼,從而大大提高了 Java 方法的運行速度。
從 1.4.2 到 5.0 版,許多改進和改變提高了 IBM JIT 編譯器的性能,同時減小了 JIT 編譯對正在運行的應用程序的影響。表 1 列出了主要的變化:
表 1. IBM JIT 編譯器中的改進
1.4.2 5.0 在正在執行 Java 方法的 Java 線程上同步地編譯 使用單獨的異步編譯線程 在需要時對方法進行編譯 方法排隊進行編譯 對於 Java 方法,使用本機堆棧 維護一個單獨的 Java 堆棧 只有一種編譯優化 共有 5 個編譯優化級別 方法只能編譯一次 可以進行重新編譯 可能出現包含循環的堆棧上方法替換 沒有堆棧上替換 當應用 -Xdebug 時,禁用 JIT 在調試模式中,JIT 繼續采用降低的優化級別進行編譯
在這些改進中,主要的新特性是異步編譯、多級優化和分析驅動的重新編譯。
異步編譯
Java 方法的 JIT 編譯現在在一個獨立於進行調用的線程的專用線程上異步地執行。這意味著,調用某個 Java 方法從而觸發編譯的線程在編譯期間不再被阻塞。方法被添加到編譯隊列中,而線程可以繼續運行並執行方法的非編譯版本。方法編譯之後,對方法的下一次調用會運行 JIT 編譯的版本。為了確保頻繁使用的方法優先編譯,隊列常常會重新調整次序並重新確定優先級。
在多處理器計算機上,異步編譯可以大大提高啟動時的性能,因為應用程序啟動階段的大部分是單線程的,而 Java 方法的編譯由一個單獨的處理器承擔。
多級優化
現在,JIT 編譯器能夠采用 5 個優化級別之一對 Java 方法進行編譯。通過使用單獨的采樣線程,編譯器判斷特定的 Java 方法花費多長時間,由此判斷這個方法是否需要 JIT 編譯,以及應該采用哪個優化級別進行編譯。最常執行的 Java 方法采用最高的優化級別進行編譯,這會獲得最大的性能收益,但是完成編譯所花費的時間成本也最大,內存需求也可能更高。不經常使用的方法采用較低的優化級別進行編譯,這樣編譯完成得很快並產生顯著的性能收益。
分析驅動的重新編譯
如果由於應用程序運行方式的改變,Java 方法需要以更高的優化級別進行編譯,那麼 JIT 編譯器能夠對它們進行重新編譯。在最高優化級別上,根據從正在執行的代碼自動生成的動態分析數據執行重新編譯。這些信息反映了方法本身實際上是如何被使用的。在收集短期數據之後,使用此數據對方法進行重新編譯並相應地優化。
虛擬機改進
在 Java 5.0 技術的 IBM 實現中,與其他組件一樣,虛擬機也得到了大量改進。兩個最重要的改進是共享類的實現以及分析和調試方面的新特性。
共享類
共享類原來只在 z/OS 和 OS/390 的 Java 平台 IBM 實現中可用。共享類的這種實現已經被廢棄,替換為所有平台上的新實現。
新的實現在共享內存中維護一個靜態類數據緩存,它可以在實現新的共享類功能的所有 IBM Java 運行時之間共享;這個緩存在 Java 運行時的調用之間持續存在。共享類功能應用於所有 JCL 和基於類路徑的類,而且通過使用簡單的 API 很容易應用於定制類裝載器裝載的類。在緩存已經填充之後,這種功能可以減少內存占用和啟動時間。對於在一台計算機上有多個 Java 運行時,或者運行時常常重新啟動的情況,這個特性尤其有效。
分析和調試
除了支持 Java Debug Wire Protocol(JDWP)、JVM Profiling Interface(JVMPI)和 JVM Tools Interface(JVMTI)之外,IBM 調試器實現還有另外兩個特性:高速調試(high-speed debug) 和熱代碼替換(hot-code replace)。
高速調試允許在運行 Java 調試器的同時執行 Java 方法的 JIT 編譯,而且這種編譯可以采用幾乎完全的優化。這在調試大型 Java 應用程序時很有用,例如那些在 J2EE 堆棧上運行的應用程序;在這些應用程序中如果禁用 JIT,那麼調試時的性能會很差。
熱代碼替換允許在調試器下動態地修改源代碼,並立即運行新代碼而不需要重新啟動應用程序。
能夠在調試器下幾乎全速地運行代碼並動態地修改代碼,這大大提高了應用程序開發的效率。
可靠性、可用性和可服務性改進
對於對 Java 應用程序和 Java 運行時本身進行監視和故障調試,Java 平台的 IBM 實現已經提供了基礎設施和工具。Java 運行時中已經添加了許多這方面的改進,包括寫入內部緩沖區的連續低級跟蹤(作為第一次失敗數據捕捉的輔助措施)、一個用於監視本機內存使用情況的新選項和一個強大的 JNI 代碼檢驗器,並對轉儲和跟蹤引擎進行了重新設計。5.0 的實現還添加了一個基於 Java 的工具 API,用於查詢系統轉儲文件,這使工具開發人員能夠訪問轉儲中關於對象、線程、鎖等的信息,而不需要掌握 JVM 的內部結構。
跟蹤引擎
跟蹤引擎已經重新改造過了,並添加了一個內部 “flight recorder”。它連續地將關鍵的 VM 和 JCL 跟蹤點寫入每個線程專用的回繞型緩沖區。GC 數據也寫入一個單獨的回繞型緩沖區,從而確保可以很容易地獲得此數據,並提供一定的 GC 歷史數據。
除了 VM 的內部跟蹤之外,方法跟蹤功能能夠跟蹤 Java 代碼(包括 JCL 提供的代碼和應用程序代碼)的進入方法和退出方法事件。這不需要對應用程序代碼做任何修改,它會提供時間戳、線程 ID 和參數信息。
轉儲引擎
通過對轉儲引擎的重構,可以觸發轉儲的事件數量從 3 個增加到了 14 個。現在,可以在發生許多事件時生成轉儲,比如停止 VM、裝載和卸載類、啟動和停止線程、GC 周期以及拋出/捕獲/未捕獲異常。這些新功能與觸發事件的能力相結合,就能夠非常靈活地控制 Java 運行時何時創建轉儲以及創建什麼類型的轉儲。
DTFJ Tooling API
Dump Toolkit and Framework for Java(DTFJ)是一個基於 Java 的 API,用來訪問 Java 進程的系統轉儲中的事後信息。這使工具開發人員能夠訪問轉儲中關於系統、進程、Java VM 和 Java 應用程序的信息,而不需要了解相關結構在內存中是如何布局的。這樣就能夠編寫更好的事後分析工具。
結束語
IBM Developer Kit for Java 5.0 引入了大量特性和功能。其中一些改進(包括性能和可靠性改進)會在從以前版本遷移到 5.0 時透明地提供給用戶,其他改進需要調用。在本系列以後的幾期中,我們將深入討論 IBM 提供的一些增值改進的技術,包括垃圾收集策略、共享類和調試特性,以及如何利用它們。