在馴服 Tiger 的這一期中,John Zukowski 介紹了最新的 Java 虛擬機如何改善啟動時間、降低內存需求、提高性能。Tiger 提供了共享的數據檔案文件、新的線程調度算法以及致命錯誤處理器(用來處理故障)。請在本文附帶的 討論論壇 上與作者和其他讀者分享您對本文的想法。(也可以單擊本文頂部或底部的 討論 訪問討論論壇。)
致命錯誤處理器
JVM 包含幾個新的命令行選項。其中一個不太標准的選項是“致命錯誤處理器”。用 -XX:OnError 選項啟動 JVM,可以指定在發生錯誤和 JVM 有故障時要執行的命令。清單 1 顯示了幾個這樣的選項:
清單 1. 一些 OnError 處理選項
-XX:OnError="gcore %p; dbx - %p"
-XX:OnError="gdb %p"
-XX:OnError="pmap %p"
JVM 中發生致命錯誤時,就會執行引號中的命令。%p 選項用進程 ID 替換。想故意制造些錯誤並不容易,但是如果偶然發現某種情況確實重復發生,那麼通過處理器得到的信息是極有幫助的。
OnError 選項使用 Java 調試接口(Java Debug Interface,JDI) 服務性代理連接器橋(serviceability agent connector bridge)。有了這個橋,就能夠連接核心文件或者已經安裝的虛擬機,以及其他任務。如下所示,隨 JDK 提供了幾個診斷工具,它們利用了這個橋,但是最後三個工具在 Microsoft Windows 平台上不能用:
jps —— 得到進程 ID
jstat —— 得到進程 ID 的統計值(jstat -gc pid)
jinfo —— 得到 JVM 的配置信息
jmap —— 打印庫的內存映射
jstack pid | core —— 生成堆棧跟蹤
類數據共享
為了幫助改善啟動時間,JVM 現在和內存映射文件一起工作。這些文件在安裝時創建,保存著系統類的內部表示方式。這樣,在啟動 JVM 時,就不會從頭開始裝入系統類,而是裝入內存映射文件。這在兩個方面提供了幫助。首先,差不多有一半內存映射文件是只讀的,這意味著可以在多個並發運行的進程之間共享它們,從而在相當程度上減少了啟動時間和需要的全部內存。其次,由於這些文件采用了 Java HotSpot 虛擬機能夠使用的格式,所以永遠不會發生需要內存來處理原始類文件的情況,這也改善了啟動時間。
共享檔案文件的位置依賴於平台。文件命名為 classes.jsa,其中 JSA 擴展代表 Java 共享檔案(Java Shared Archive)。在 Microsoft Windows 平台上,可以在 jre\bin\client 中找到共享檔案。在 Linux 平台上,位置是 jre/lib/[arch]/client。所有這些位置都基於 JAVA_HOME 根。
如果不喜歡這個文件,可以刪除它。實際上,如果想重新生成這個文件,就必須刪除它。請用 -Xshare:dump 命令行開關調用 java 命令來生成它。在執行的時候,可以得到類似清單 2 所示的輸出 :
清單 2. 重新生成共享數據文件的命令輸出
Loading classes to share ... done.
Rewriting and unlinking classes ... done.
Calculating hash values for String objects .. done.
Calculating fingerprints ... done.
Removing unshareable information ... done.
Moving most read-only objects to shared space at 0x2aad0000 ... done.
Moving common symbols to shared space at 0x2ae2e848 ... done.
Moving remaining symbols to shared space at 0x2af51148 ... done.
Moving string char arrays to shared space at 0x2af51bd8 ... done.
Moving additional symbols to shared space at 0x2afd2ef0 ... done.
Read-only space ends at 0x2b027960, 5601632 bytes.
Moving read-write objects to shared space at 0x2b2d0000 ... done.
Moving String objects to shared space at 0x2b825be8 ... done.
Read-write space ends at 0x2b8643a8, 5850024 bytes.
Updating references to shared objects ... done.
如果不喜歡擁有共享的文件,可以不用它。可以用 -Xshare:off 選項禁止共享。然後,會從 rt.jar 文件裝入所有的原始類信息。
為確保允許使用共享數據文件,請使用 -Xshare:on 選項。默認情況下並沒有打開它的使用。相反,-Xshare:auto 選項是默認值。auto 選項意味著如果可能的話,應當用共享文件。如果文件不可用(例如,文件已經刪除),就會忽略該選項。
如果手動地控制共享類數據文件的使用,那麼請當心命令行選項在未來的發行版中可能會變化 —— 至少它的文檔是這麼說的。
Sun 聲稱在啟用的時候,啟動時間大約改善了 30 %。
Solaris 線程優先級
Java Thread 類包含一個 setPriority() 方法,有助於為創建的線程分配一個優先級。關於 setPriority() 方法需要了解的關鍵是:它的設置可以被忽略。而且,不同的平台有不同的線程調度模型,所以會產生不同的結果。
在 5.0 發行版中,針對 Solaris 平台的線程優先級實現方式做了一些修改,返回到一種更原始的行為上。因此,從 10 個邏輯的 Java 優先級(從 Thread.MIN_PRIORITY 到 Thread.MAX_PRIORITY)到本機優先級的映射是不同的:
Java 優先級范圍在 5 到 10 之間的線程被映射為高
Java 優先級范圍在 1 到 4 之間的線程被映射為低
就是這麼簡單 —— 如果覺得優先級更少就會更簡單的話。這個變化背後的理由是:為了保證 Java 線程可以被調度成按照與本機線程和進程相同的優先級運行。但是這就意味著在 5 到 10 之間的 Java 優先級處理起來沒有差別。
如果是在 Linux 或 Microsoft Windows 平台上,那麼可以忽略這個變化。但是,如果用戶是在 Solaris 平台上,那麼還是應當注意這個差別。
服務器類別探測
默認情況下,除非使用的是 64 位芯片(它默認使用服務器虛擬機),否則 JVM 啟動時用的是客戶端 HotSpot 虛擬機。可以使用 -server 命令行選項強制啟用服務器 HotSpot 虛擬機。如果使用的是 32 位的 Solaris SPARC 機或者 Linux/Solaris i586 系統,而且沒有指定 -client 或 -server 選項,那麼虛擬機會自動檢測要使用哪個版本。Microsoft Windows 默認采用客戶端虛擬機。
在使用自動探測時,如果一台機器擁有至少兩個 CPU 和 2GB 物理內存,就會把它當成服務器。如果不介意較慢的啟動時間,在沒有最近出現的共享內存時,服務器虛擬機運行一段時間會變快。
垃圾收集
如果確實在運行服務器虛擬機,或者至少是在服務器級別的機器上,那麼默認的垃圾收集器就會從老式的串行版本(-XX:+UseSerialGC)變成新式的並行收集器(-XX:+UseParallelGC)。其他與堆尺寸和時間限制有關的默認設置也發生了變化。
結束語
我們完成了這個技巧,同時又回到起點。隨著每個 Java 的發行版,事情都會有些變化。虛擬機的配置有了新的、不同的選項,承諾會有更快的啟動、更小的內存開銷。J2SE 平台的 5.0 發行版也遵循這個模式。