程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 實時Java,第1部分: 使用Java語言編寫實時系統

實時Java,第1部分: 使用Java語言編寫實時系統

編輯:關於JAVA

由於很多重要原因,Java 語言在實時系統中的應用非常有限。這些原因包括 Java 語言設計中固有的不確定性性能影響,例如動態類加載,以及 Java 運行時環境(Java Runtime Environment,JRE)本身的不確定性性能影響,例如垃圾收集器和本地代碼編譯。Real-time Specification for Java (RTSJ) 是一種開放的規范,它進一步增強了 Java 語言的開放性,使它能夠用來構建實時系統(參見 參考資料)。要實現 RTSJ 規范,要求具備操作系統、JRE 和 Java 類庫(Java Class Library,JCL)的支持。本文探究了使用 Java 語言實現實時系統存在的挑戰,並介紹了能夠應對這些挑戰的開發工具包和運行時環境。本系列後續文章將更深入解釋本文介紹的這些概念和技術。

實時需求

Real-time (RT) 是一個含義廣泛的術語,用來描述需要與真實世界同步的應用程序。比如,一個反應緩慢的用戶界面不能夠滿足一般用戶的正常 RT 需求。這類應用程序通常被稱為軟 RT 應用程序。“應用程序對鼠標單擊的反應時間不能超過 0.1 秒”,這樣表述可能更明白一些。如果不能滿足這種需求,那麼這就是一個軟故障:應用程序可以繼續運行,盡管用戶不高興,但仍然能使用它。相比之下,那些必須嚴格地滿足實時同步需求的應用程序通常被稱為硬 RT 應用程序。比方說,控制飛機方向的應用程序不能夠有任何原因的延遲,否則將導致災難性的後果。RT 應用程序的含義很大程度上取決於應用程序的容錯程度,即偏離實時需求到何種程度時會被認為是錯誤。

RT 需求的另一個關鍵因素是響應時間。對於編寫硬或軟 RT 應用程序的編程人員來說,理解響應時間的約束至關重要。要求滿足 1 微秒硬響應的技術與那些要求滿足 100 毫秒硬響應的技術截然不同。在實踐中,要使響應時間小於幾十微秒,需要組合定制的軟硬件,很可能沒有(或者有非常薄的)操作系統層。

最後,健壯 RT 應用程序的設計者通常需要一些可計量的確定的性能特征,以便設計能夠滿足響應時間需求的應用程序。不可預測的性能影響非常嚴重,致使系統無法滿足應用程序響應時間的要求,因此難於(或者根本不可能)正確地設計應用程序。大多數 RT 執行環境的設計者投入了很多精力來減少不確定性性能影響,來滿足最大范圍內 RT 應用程序的響應時間需求。

RT Java 應用程序面臨的挑戰

在通用操作系統中,運行在通用 JVM 上的標准 Java 應用程序只能期望滿足幾百毫秒級別的軟 RT 需求。原因涉及該語言的幾個基本方面:線程管理、類加載、Just-in-time (JIT) 編譯活動以及垃圾收集(garbage collection,GC)。應用程序設計者可以減輕其中的一些問題,但需付出大量的工作。

線程管理

標准的 Java 語言沒有為線程調度和線程優先級提供任何保證。一個必須在精確時間內響應事件的應用程序無法確保不會在一個高優先級線程之前調度另一個低優先級線程。為了彌補這個缺點,程序員需要將應用程序劃分為一組子應用程序,這樣操作系統才可以在不同優先級上運行。這種劃分將增加事件的開銷,並使得事件間的通信更加困難。

類加載

一個與 Java 一致的 JVM 必須延遲加載類,直到程序第一次引用該類。根據被加載類所在的介質(磁盤或其他)的速度、類的大小、類加載器本身的開銷,類加載的時間有所不同。加載類的延遲通常高達 10 毫秒。如果需要加載幾十或幾百個類,則加載時間本身就會引起很長時間的意外延遲。仔細地設計應用程序,使應用程序在啟動時加載所有的類,但是這必須手動完成,因為 Java 語言規范不讓 JVM 提前執行這一步。

垃圾收集

應用程序開發中 GC 的益處 —— 包括指針安全、避免內存洩露以及使開發人員免於編寫定制的內存管理工具 —— 已經被很好地證明。然而,對於使用 Java 語言的硬 RT 編程人員來說,GC 是使他們備受挫折的另一個原因。當 Java 堆耗盡後仍然不能滿足分配請求時,將自動進行垃圾收集。應用程序本身也能觸發垃圾收集。

一方面,GC 對於 Java 程序員來說非常不錯。在諸如 C 和 C++ 這樣的語言中,由於需要明確地管理內存而引發的錯誤是最難診斷的一些問題。在部署應用程序時,檢驗是否存在這類錯誤同樣也是一個基本難題。Java 編程模型的一個主要優點就是:由 JVM 而不是應用程序來執行內存管理,這將為應用程序編程人員去掉這一負擔。

另一方面,傳統的垃圾收集器可導致長時間的延遲,而應用程序編程人員幾乎不可能預測出這一時間。幾百毫秒的延遲並不少見。在應用程序層上解決這一問題的惟一方法就是通過創建一組重用對象來阻止 GC,從而確保不會耗盡 Java 堆的內存。換言之,編程人員放棄了使用 JVM 管理內存這一優點,而是通過親自明確地管理內存來解決問題。實踐中,這個方法通常不奏效,因為它阻止編程人員使用 JDK 和其他類供應商提供的眾多類庫,這可能會創建大量的臨時對象從而最終將堆填滿。

編譯

將 Java 代碼編譯為本地代碼引發了與類加載類似的問題。大多數現代 JVM 開始先解釋 Java 方法,然後僅將頻繁執行的方法編譯成本地代碼。延遲編譯促成了快速啟動,並減少了應用程序運行期間執行的編譯數量。但是使用解釋後的代碼執行任務和使用編譯後的代碼執行任務在時間上有巨大的差異。對於硬 RT 應用程序來說,由於無法預測何時發生編譯,將導致很大程度的不確定性,從而無法有效地規劃應用程序的行為。對於類加載,通過在應用程序啟動階段使用 Compiler 類以編程的方式編譯方法可以減輕這一問題,但是維護這樣的方法非常乏味並且容易發生錯誤。

Java 實時規范

RTSJ 的創建是為了解決 Java 語言的一些限制,這些限制阻止了它在 RT 執行環境中的廣泛應用。RTSJ 處理了幾個有問題的地方,包括調度、內存管理、線程、同步、計時、時鐘和異步事件處理。

調度

RT 系統需要嚴格控制線程的調度方式並保證線程調度的確定性:就是說,按照給定的相同的一組條件調度線程。盡管 JCL 定義了線程優先權的概念,然而傳統 JVM 並不需要執行優先權。同樣,非 RT Java 實現通常使用循環搶占式調度方法,該方法根據不可預測的調度順序進行調度。在 RTSJ 規范中,RT 線程需要真正的優先級,以及具有優先級繼承支持的固定優先級搶占式調度程序。這種調度方法確保了最高優先級的活動線程總是被執行,並且它能夠一直執行直到自願釋放 CPU 或者被更高優先級的線程搶占。優先級繼承確保了在高優先級線程需要的資源被低優先級線程占用時,避免發生優先級反轉。優先級反轉在 RT 系統中是一個很嚴重的問題,因此我們將在 RT Linux® 中更詳細地討論它。

內存管理

雖然一些 RT 系統能夠容忍由垃圾收集器引起的延遲,但是在很多情況下,延遲是不可接受的。為了支持那些不能容忍被 GC 打斷的任務,RTSJ 定義了不朽(immortal)和作用域(scoped)內存區域,以補充標准的 Java 堆。如果垃圾收集器需要釋放堆中的內存,則這些內存區域將允許任務使用內存而不需要中斷任務。分配在不朽內存中的對象可以被所有的線程訪問並且從來不會被收集。正由於它永遠不會被收集,不朽內存是一個有限制的資源,必須謹慎使用。程序員可以控制作用域內存區域的創建和銷毀。每個作用域內存區域按最大空間分配,並且可以用於對象分配。為了確保對象之間引用的完整性,RTSJ 定義了管理一個內存區域(堆、不朽內存、作用域內存)中的對象如何引用其他內存區域對象的規則。更多的規則定義了作用域內存中的對象何時終結,以及何時可以重用內存區域。由於這些復雜性,建議只對那些不能夠忍受 GC 暫停的組件使用不朽和作用域內存。

線程

RTSJ 增加了對兩個新線程類的支持,它們為使用 RT 行為 RealtimeThread 和 NoHeapRealtimeThread(NHRT) 執行任務提供基礎。這兩個類為優先級、周期性行為、處理程序時限(當超過時限時將觸發程序)以及除堆外的內存區域的使用提供了支持。NHRT 不能訪問堆,因此,與其他類型線程不同,NHRT 很少會被 GC 打斷或搶占。RT 系統通常將具有高優先級的 NHRT 用於那些延遲需求最嚴格的任務。將 RealtimeThread 用於那些延遲需求能夠適應垃圾收集器的任務,將常規 Java 線程用於其他任務。由於 NHRT 不能訪問堆,因此使用這些線程必須非常謹慎。例如,即使使用標准 JCL 中的容器類也必須小心,以避免容器類無意中為堆創建臨時或內部對象。

同步

在 RT 系統中必須小心地管理同步,以防止高優先級線程等待低優先級線程的情況發生。RTSJ 包括了優先級繼承支持,可以在發生同步時進行管理,並且還提供了線程通信的能力,而不需要通過無等待(wait-free)讀寫隊列實現同步。

計時和時鐘

與標准 Java 代碼提供的時鐘相比,RT 系統需要更高分辨率時鐘。新的 HighResolutionTime 和 Clock 類封裝了這些計時服務。

異步事件處理

RT 系統通常管理並響應異步事件。RTSJ 包含了對異步事件處理的支持,觸發異步事件的事件包括大量包含計時器的源代碼、操作系統信號、超過時限和其他應用程序定義的事件。

IBM WebSphere Real Time

要實現 RTSJ 需要來自底層操作系統及 JRE 組件的廣泛支持。在 2006 年 8 月發布的 IBM® WebSphere® Real Time(參見 參考資料)與 RTSJ 全面兼容,並且包含了一些新技術,旨在改進 RT 系統的運行時行為並減少應用程序設計者創建 RT 系統時必須的工作量。圖 1 展示了 WebSphere Real Time 組件簡化後的表示:

圖 1. WebSphere Real Time 概覽

WebSphere Real Time 的基礎是 IBM 的跨平台 J9 技術。Linux 操作系統應用了開源的 RT 補丁,提供了基本的 RT 服務以支持 RT 行為,特別是支持 RTSJ 規范要求的行為。得到極大加強的 GC 技術支持 1 毫秒的暫停時間。JIT 編譯可用於軟 RT 場景,其中編譯將在不需要執行高優先級任務時發生。同樣還引入了新的 Ahead-of-time (AOT) 編譯技術(圖 1 沒有顯示),為不適合使用 JIT 編譯的系統提供了硬 RT 性能。接下來的幾節將介紹每一種技術;本系列後續文章將詳細闡述每種技術的工作原理。

RT Linux

WebSphere Real Time 運行在一個定制的完全開源的 Linux 版本上。其中進行了幾處更改用以創建 RT Java 環境。這些更改提供了完全搶占式內核、線程中斷處理程序、高分辨率計時器、優先級繼承和健壯的互斥鎖(mutex)。

完全搶占式內核

RT Java 線程是使用固定優先級 調度實現的,又稱為靜態優先級 調度,它使用先進先出(first-in-first-out)的調度策略。標准的 Linux 內核提供了軟 RT 行為,盡管並沒有保證高優先級線程在搶占低優先級線程時等待時間的上限,但可以粗略估算出時間為幾十微秒。在 RT Linux 中,幾乎每個內核行為都是搶占式的,因此需要減少低優先級線程被搶占的時間,並允許高優先級線程運行。剩下的不能被搶占的臨界部分非常短,並且可以確定地執行。這三個等級的執行順序將改善 RT 調度延遲,並且可以粗略測量為幾十微秒。

減少延遲的線程中斷處理程序

幾乎所有的中斷處理程序都被轉換為運行在進程中的內核線程。由於處理程序變為用戶可配置的、可調度的實體,可以像其他任何進程一樣被搶占和優先化,因此延遲變得更短並且更具確定性。

高分辨率計時器

高分辨率時鐘和計時器提供了更高的分辨率和准確性。RT Java 將這些特性用於高分辨率休眠和定時等待。Linux 高分辨率計時器是使用高精度、64 位數據類型實現的。與傳統 Linux 不同(其時鐘和計時器依賴於低分辨率的系統滴答,限制了計時器事件的粒度),RT Linux 使用獨立的可編程的高分辨率計時器事件,可以實現每微秒以內的計時。

優先級繼承

優先級繼承技術可以避免常見的優先級反轉問題。圖 2 最上面的圖表解釋了一個最簡單的優先級反轉示例,它涉及三個線程:一個高級(H)、一個中級(M)和一個低級(L)優先級線程。設想 H 和 M 最初等待事件被觸發而處於休眠狀態,而 L 處於活動狀態並持有一個鎖。如果 H 被喚醒去處理事件,它將搶占 L 並開始執行。在這裡考慮一下如果 H 在 L 持有的鎖處出現阻塞會發生什麼情況。由於只有 L 釋放了鎖後 H 才能進行處理,H 被阻塞,而 L 再次開始執行。如果此時 M 被事件觸發,M 將搶占 L 並且執行時間取決於其需要。這種情況就被稱為優先級反轉,因為 M 在 H 之前執行,即使 H 比 M 的優先級高。

圖 2. 優先級反轉和優先級繼承示例

RT Linux 通過使用名為優先級繼承(也稱作優先級借出(priority lending))的策略防止發生優先級反轉,圖 2 底部的圖表做了解釋。當 H 在 L 持有的鎖處阻塞時,H 將其優先級交給 L,這將保證在 L 釋放 H 需要的鎖之前,比 H 優先級低的任務都不能搶占 L。當 L 釋放鎖後,L 的優先級恢復到最初的值,這樣 H 就可以進行處理而不需要等待 L 完成。應用程序設計者應該盡量避免發生高優先級線程所需的資源被低優先級線程占有的情況,但是優先級繼承機制增強了健壯性,從而防止了優先級反轉的發生。

健壯的互斥鎖和 rt-mutexes

快速用戶空間互斥(也被稱為 futexes)支持 Linux pthread 互斥鎖。Futexes 對獲得非競爭鎖的時間進行了優化,而不用依賴內核;只有對競爭的鎖才需要內核干涉。健壯的互斥鎖解決了在持有鎖的應用程序崩潰後正確清理鎖的問題。同樣,rt-mutexes 對優先級繼承協議擴展了健壯的互斥鎖,後者允許 RT JVM 通過 pthread 庫依賴優先級繼承行為。

確定性垃圾收集

假設一個 RT 操作系統(比如 RT Linux)為 RT 行為提供了基礎,那麼可以構建 JVM 的其他主要部分來呈現 RT 行為。GC 是 JVM 中造成不確定性行為主要原因之一,但是這種不確定性可以通過謹慎地設計和使用 RT Linux 的特性而減輕。

GC 暫停的不確定性影響將嚴重破壞 RT 應用程序在特定時限內完成任務的能力(請參見 垃圾收集)。多數 GC 實現將妨礙 RT 應用程序實現其延遲目標,只有同步需求寬松的大型任務才承受得起 GC 技術引起的延遲。RTSJ 對這一問題的解決方案就是引入由編程人員管理的內存分配,方法是使用不朽和作用域內存區域以及 NHRT,但是這個解決方案對於 Java 應用程序設計者來說開始成為巨大的負擔。

WebSphere Real Time 使編程人員依賴於 RTSJ 內存區域(如果他們願意的話),但是推薦您只對延遲需求極度嚴格的任務使用這種方法。對於允許 GC 暫停時間為 1 毫秒級的任務,IBM 創建了確定性 GC 技術,該技術可以使編程人員從輕松地編寫自動內存管理和 具有可預測性能的管理任務中獲益。

IBM 的確定性 GC 技術基於兩個簡單的前提:

沒有一個 GC 暫停超出了最大上限。

GC 消耗的時間要不多於給定時限,方法是控制該時限內暫停的次數。

牢記這兩個前提並用之管理 GC 行為將顯著增加應用程序實現其 RT 目標的可能性。

Metronome GC

WebSphere Real Time 使用 Metronome GC 實現 JVM 中的確定性低暫停時間的 GC 行為(參見 參考資料)。Metronome GC 使用基於時間的調度方法,該方法在固定的調度中交叉運行收集器和應用程序(GC 稱之為 mutator,因為從垃圾收集器的角度看,應用程序的行為就是隨時間改變活動對象的圖表)。

針對時間而不是分配率進行調度的原因是:在應用程序執行期間,分配率通常都是不均勻的。完成 GC 工作對分配來說是個負擔,GC 暫停很可能是不均勻分配的,這樣降低了 GC 行為的確定性。通過使用基於時間的調度,Metronome GC 可以實現一致的、確定性的、有限的暫停時間。並且,由於不需要任何語言擴展或對現有代碼做修改,常規 Java 應用程序可以透明地使用 Metronome 並從其確定性特征受益。

Metronome 將時間分為一些離散的量子(quanta),大約 500 微秒但是不會超過 1 毫秒,專門用於 GC 或者應用程序。盡管量子非常的短,如果將一些量子用於執行 GC 任務,那麼應用程序仍然會感覺到較長的暫停時間,這會影響到 RT 時限。為了更好地支持 RT 時限,Metronome 將量子分配給 GC,而應用程序應該具有最小的時間百分比。這個百分比被稱為 utilization,是用戶提供的參數。在任何時間間隔內,用於應用程序量子數不得少於指定的 utilization。默認情況下,utilization 值為 70%:在任何 10 毫秒的時限內,至少有 7 毫秒專門用於應用程序。

用戶可以在啟動應用程序時設置 utilization 的值。圖 3 展示了較長時間期限內應用程序 utilization 的示例。注意垃圾收集器活動狀態下對應於時間量子的周期性下降。圖 3 所示的整個時間期限內,應用程序 utilization 保持在指定的 70%(0.7)或以上。

圖 3. 示例 utilization 圖表

圖 4 演示了使用 Metronome 技術後的 GC 暫停時間的確定性。只有一小部分暫停超過了 500 微秒。

圖 4. GC 暫停時間直方圖

為保持較短的單次 GC 暫停時間,Metronome 在堆內使用了寫屏障(write barrier)以及相關的亞結構(metastructure)來跟蹤活動的和可能死亡了的對象。跟蹤活動的對象需要一些 GC 量子來確定應該保持哪些對象為活動的,哪些對象應該回收。由於這種跟蹤工作與程序的執行是交叉進行的,應用程序通過執行加載或存儲將某些對象 “隱藏”,因此 GC 可能會丟失這些對象的跟蹤。

隱藏活動的對象並不一定是惡意應用程序代碼的結果。這種現象很常見,因為應用程序沒有意識到垃圾收集器的行為。為了確保收集器不會丟失對象,GC 和 VM 互相協作,跟蹤對象之間的鏈接,因為它們是通過存儲應用程序執行的操作而被創建和銷毀的。在應用程序執行存儲操作之前執行寫障礙將完成這種跟蹤。寫障礙的目的就是:如果存儲將導致隱藏活動的對象,那麼就記錄對象鏈接狀態的變化。這些寫障礙表示平衡確定性行為的性能和內存占用開銷。

大型對象的分配對很多 GC 策略都是件棘手的事情。在很多情況下,堆中存在太多碎片以至於無法存放一個大的對象,比如說數組。因此,必然會引發長時間的暫停來整理碎片或進行壓縮,堆將很多小塊的自由內存區域整合為一塊大的自由內存區域,從而滿足大型分配請求。Metronome 將一種新的兩級對象模型用於 arraylets 數組。arraylets 將大數組分為一些小的數組,以使沒有進行碎片整理的堆更容易滿足大數組分配請求。arraylets 對象的第一級稱為 spine,包含指針列表,指向數組的更小部分,稱為 leaves。每一個 leaf 大小相同,這樣簡化了查找數組中特定元素的計算,並使收集器更容易查找到合適的自由空間來分配每個 leaf。將數組分成更小的不連接的部分,這樣使數組可以分配在很多小的自由區域裡(堆中經常會有這樣的區域),而不需要進行壓縮。

與傳統的 STW 垃圾收集器實現不同,Metronome 在整個應用程序生存期內連續執行 GC 過程,而前者則使用 GC 周期的概念表示垃圾收集的開始和結束。在整個應用程序生存期內,都保證了應用程序的 utilization,其值要高於不需要進行太多的 GC 工作情況下 utilization 的最小值。隨著收集器將找到的自由內存返回給應用程序,自由內存量不斷上下變化。

RT 中的本地代碼編譯

大多數現代 JVM 結合使用了解釋和編譯的代碼執行。為了消除解釋的高性能代價,JIT 編譯器選擇將頻繁執行的代碼直接轉換為 CPU 的本地指令。Java 語言的動態特性通常會將編譯器的操作作為程序執行,而不是作為發生在程序運行前的一個步驟(C++ 或 Fortran 語言中就屬於後一情形)。JIT 編譯器將選擇它要編譯的代碼,這樣它花費的編譯時間很可能通過對代碼性能的改進得到補償。在動態編譯行為的最頂層,傳統 JIT 編譯器使用了大量的推測性優化,這些優化利用了正在運行程序的動態特性,這對於應用程序執行的某個階段中的一點也許是正確的,但是不一定適合整個應用程序的執行階段。如果對於該特性的假設稍後變為錯誤,那麼這種優化是 “未完成的”。

在傳統的非 RT 環境中,在程序執行時編譯代碼效果非常好,因為大部分的編譯器操作對應用程序性能是透明的。然而,在 RT 環境中,JIT 編譯器引入了不可預知的運行時行為,這將嚴重影響最壞情況時執行時間分析。但是這種環境中,編譯的代碼的性能優勢仍然非常重要,因為它能夠使更復雜的任務在較短的時間內完成。

WebSphere Real Time 引入了兩種解決方案在不同的折中點平衡這兩種需求。第一個解決方案是使用 JIT 編譯器,在較低的非 RT 優先級上運行,它已被修改為很少執行主動的推測性優化。非 RT 優先級的操作使操作系統保證編譯器不會干擾 RT 任務的執行。但是,代碼性能會隨時間改變這一事實是一種不確定影響,這使得這種解決方案更適合於軟 RT 環境而不適合硬 RT 環境。

對於硬 RT 環境來說,WebSphere Real Time 引入了 AOT,用於應用程序編程。存儲在 JAR 文件中的 Java 類文件可以通過簡單的命令行預編譯為 Java eXEcutable (JXE) 文件。通過將這些 JXE 文件(而不是原始的 JAR 文件)指定到應用程序類路徑中,將能夠使應用程序得到調用,因此就執行了 AOT 編譯的代碼 —— 而不是經解釋過的字節碼或 JIT 編譯器編譯過的本地代碼。在第一個 WebSphere Real Time 發布版中,使用 AOT 代碼意味著沒有使用 JIT 編譯器,這樣有兩個主要的好處:更低的內存消耗以及不會受到來自 JIT 編譯線程和樣例線程的動態性能影響,這些線程用來標識頻繁執行的代碼。

圖 5 展示了在使用 AOT 代碼時,在 WebSphere Real Time 中如何執行 Java 代碼:

圖 5. 如何使用 AOT 代碼

首先看圖 5 的左上方,和任何 Java 開發項目一樣,開發人員將 Java 源代碼編譯成類文件,類文件被綁定到 JAR 文件,然後由 AOT 使用 jxeinajar 工具對其進行編譯。這個工具可以編譯 JAR 文件中所有類的所有方法,也可以根據一個基於 JIT 的樣例程序的執行輸出選擇性地編譯某些方法,這個程序標識了需要編譯的最重要的方法。jxeinajar 工具編譯 JAR 文件中的方法並構造一個 JXE 文件,後者包含原始 JAR 文件的內容以及 AOT 編譯器生成的本地代碼。在執行程序時,JXE 文件可以直接取代 JAR 文件。如果使用 -Xnojit 選項調用 JVM,那麼將加載類路徑中 JXE 文件的 AOT 編譯的代碼(根據 Java 語言的規則)。在程序執行期間,將對從 JAR 文件加載的方法或者從 JXE 文件加載的未經編譯的方法進行解釋。將從 JXE 中加載的編譯了的方法作為本地代碼執行。在圖 5 中,同樣還需要使用 -Xrealtime 命令行選項指定應該調用 RT VM。該命令行選項只有在 WebSphere Real Time 中可用。

AOT 代碼的缺點

盡管 AOT 代碼能夠產生更確定的性能,它同樣具有一些缺點。用於存儲 AOT 代碼的 JXE 文件通常要比存放類文件的 JAR 文件大很多,這是因為本地代碼一般要比存儲在類文件中的字節碼密度小。執行本地代碼還需要各種補充數據,用以描述如何將代碼綁定到 JVM 以及如何捕獲異常(比如),從而執行代碼。第二個缺點是,盡管 AOT 編譯過的代碼比解釋過的代碼執行速度快,但是卻比 JIT 編譯的代碼慢得多。最後一點,將解釋的方法轉換為編譯的方法所用的時間(反之亦然)要比從其他解釋方法調用一種解釋方法(或從其他編譯的方法調用編譯方法)要長。在具有活動 JIT 編譯器的 JVM 中,會通過不斷編譯編譯代碼的 “邊緣部分” ,直到轉換次數不會影響性能為止,最終消除這種代價。在具有 AOT 編譯代碼而沒有 JIT 編譯器的 JVM 中,轉換次數是由被編譯到 JXE 中的方法決定的。因此,我們通常推薦使用 AOT 編譯整個應用程序以及應用程序所依賴的 Java 庫類。正如上文提到的,擴展編譯了的方法的數量將影響內存消耗,盡管性能從中獲得的好處往往要比增加內存消耗更重要。

AOT 代碼通常要比 JIT 代碼的執行速度慢,這是由於 Java 語言自身的特性引起的。Java 語言需要執行的 程序在第一次引用類時對類進行解析。由於在程序執行前進行編譯,AOT 編譯器必須謹慎對待它所編譯的代碼所引用的類、字段和方法。AOT 編譯的代碼通常要比 JIT 編譯的代碼慢,這是因為 JIT 具有一個優點,它是在所執行的應用程序解析了大量這樣的引用之後才執行編譯的。然而,JIT 編譯器還必須注意抵消它編譯程序消耗的時間,因為這些時間將延長程序的執行時間。因此,JIT 編譯器不會使用相同的優化度來編譯所有的代碼。而 AOT 應用程序則無此限制,因此它能夠使用更主動的編譯技術,所以有時可產生優於 JIT 編譯代碼的性能。此外,AOT 能夠比 JIT 編譯器編譯更多的方法,因此 AOT 編譯也能比 JIT 編譯產生更好的性能。雖然如此,通常 AOT 編譯的代碼的速度要慢於 JIT 編譯的代碼。

要避免不確定性性能影響,WebSphere Real Time 提供的 JIT 編譯器或者 AOT 編譯器都不會使用現代 JIT 經常采用的主動的推測性優化。執行這類優化通常能夠顯著改善性能,但是並不適合在 RT 環境中使用。此外,為了全面支持 RTSJ 以及 Metronome 垃圾收集器,引入了一些開銷到編譯的代碼中,而傳統編譯器不需要執行這些代碼。鑒於以上這些原因,在 RT 環境下編譯的代碼的運行速度通常要比非 RT 環境編譯的代碼慢。

未來發展方向

在可預測性能和原始吞吐量方面,需要做更多的工作以加快 RT Java 環境。必須對 Java 語言實現兩個關鍵領域的改進,從而使其在 RT 應用程序領域內獲得成功:

為運行傳統操作系統而希望獲得更好的預測性的用戶提供 RT 技術。

使 RT 技術簡單易用。

面向軟 RT

WebSphere Real Time 的很多特性對以傳統操作系統為目標的編程人員非常有用。增量 GC 和基於優先級的線程必定會在大量應用程序中得到應用,即使只能夠提供軟 RT 性能而不能保證硬 RT 的實現。比如,對於多數開發人員來說,提供可預測性能而不會出現不可預測的 GC 延遲的應用服務器是個非常有吸引力的想法。類似地,使用合理的調度目標,使應用程序運行高優先級並良好監視的 Java 線程將簡化 Java 服務器的開發。

令 RT 更加簡單

對於開發人員來說,將 Java 語言的優點引入 RT 系統的創建中將帶來非常大的優勢。但是,始終有要改善的地方,並且我們不斷開發新的特性來進一步簡化 RT 編程。您可以訪問 IBM alphaWorks 站點試用我們研究的實時線程研究技術,它能夠使開發人員管理使用非常頻繁的事件,而這些事件對變化或延遲的要求很高(參見 參考資料)。通過預加載、預初始化以及預編譯代碼處理事件,然後使用比 RTSJ 中的 NHRT 更少的繁瑣限制運行獨立於垃圾收集器的代碼,從而實現更具確定性的行為。您還將發現名為 TuningFork 的工具,它用來跟蹤從操作系統到 JVM,再到應用程序內部的路徑,能夠更輕松地執行詳細的性能分析。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved