我在一家投資銀行工作了許多年 , 我的經驗告訴我,在金融軟件中出現的絕大多數問題是由於缺少實時支持導致的。許多大型的金融IT系統工作於JAVA平台,程序運行時一個不在計劃中的的兩秒的垃圾收集將導致成千上萬美元的損失。更糟糕的是,垃圾收集通常發生在程序負載很高的情況下,這時候程序對執行過程中的中斷更為敏感。同樣的情況也發生在其他高科技產業中,這就是為什麼需要仔細研究實時Java規范及其實現的原因。
有些人可能會認為JAVA和實時是不同環境中的兩個概念,實際上,最老的JSR之一(確切的說是第一個JSR)就是關於擴展JAVA平台的實時特性的。然而,任務提交的順序並不保證它的實現的順序;Sun只是在最近才實現了實時性,但這並不意味著它是一個低優先級的特性;實際上,這是一個非常復雜並且是一個完整的工作。但是實時的要求與Java的本身的要求兼容嗎?有很多問題就不得不提了,如GC的語義學 ,同步,線程調度以及high-resolution的時間管理。在本文中,我們將逐一解釋這些名詞。
版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
作者:michaelzyy;michaelzyy
原文:http://www.matrix.org.cn/resource/article/2006-11-28/Java+Real+Time_72f1a994-7e38-11db-babc-9753a314dd4b.Html
關鍵字:Java;Real time
實時是什麼意思呢?
Greg Bollella ,是Sun公司的一個傑出的工程師,實時Java規范的作者之一,它說,實時意味著“能夠可靠的可預測的推測和控制程序邏輯的時間行為的能力。”實時並不像許多開發者想的那樣,意味著速度,而是意味著當需要對現實世界的事件作出反應時,它的行為是可預測的和可靠的。實時的電腦總是在限定的期限之內作出反應。取決與所設定的的期限,大量的系統可以被稱作是實時的。
很多程序不能允許即使是一秒的延遲;他們包括之前提到的金融軟件,飛機控制軟件,核電站控制軟件等等。所以,這些並不完全是對速度要求很高的,盡管實時平台的設計師會努力使得程序變快。顯而易見,標准的JAVA平台並不符合這些實時系統的要求,這也寫入了J2SE和J2EE的許可證協議中,這些協議明確的聲明Java不能用於核電站設施軟件和防衛系統等等。
實時Java
開發實時應用程序需要一個能夠允許開發者正確的控制程序的運行時間以及程序在現實中的行為PI集合和語義。因此JAVA的實時版本必須提供一些JVM的增強以及一個適合實時程序的API集合。毫不奇怪,在JAVA中添加實時的特性最大的障礙在於它的垃圾收集器。Sun最近發布的JAVA實時版本RTS1.0中就包含了一個革命性的核心的實時的垃圾收集器。盡管它的第一個實現中並沒有包含這樣一個垃圾收集器(將在下一個release版本中將增加)。JAVA RTS 提出了其他一些問題,保證線程調度的確定性,overhead同步,鎖排隊管理,類初始化以及最少的中斷反應延遲。Java RTS僅僅針對於合適的操作系統,這就意味著只有諸如QNX這樣的的實時操作系統才適合去實現一個這樣的JVM。
實時JAVA規范的第一個官方商業實現版本是在Solaris 10,工作在UltrASParc硬件上,並且要求J2SE 1.4.2作為基礎。未來的版本將會支持JAVA 5 以及其他的一些平台。美國海軍,Raytheon公司和波音公司已經開始使用SUN的JAVA實時系統。當然,SUN的JAVA實時系統並不是第一個實時JAVA的實現。一些嵌入式系統的廠商已經在他們的系統中實現了一些實時的特性,不過他們的實現只是涵蓋了一些具體的需要,並不符合JSR-1規范的要求。這對於那些使用Java平台並需要實時JVM的開發人員來說是個好消息。
這些聽起來都不錯,可是從一個開發人員的角度來看,這又意味著什麼呢?要改變現有的程序使其使用RTS的API需要些什麼改變呢?我們可以擺脫垃圾收集導致的中斷這樣一個主要的問題嗎?很不幸,所有的一切並不是那麼簡單。僅僅簡單的安裝一個RTS的擴展包,把java.lang.Thread實例改名交Javax.realtime.RealtimeThread並不能把一個程序變成一個實時的應用程序。
不過,這仍然是一個很好的開端,至少你可以獲得一個革命性的實時的垃圾收集器。不得不提的是現有的J2SE的程序將可以成功的在JAVA 實時系統下運行 因為RTSJ規范只是JAVA語言規范和Java虛擬機規范的一個子集。它並不允許那些可能會破壞現有程序的語義擴展。
為了使得實時的垃圾收集器可預測,程序員必須了解它的程序是如何從堆中要求內存的,因為垃圾收集器和程序都要用到它。程序產生垃圾,然後垃圾收集器將垃圾清理成空閒的內存,它們需要在堆中進行。因此,你必須告訴垃圾收集器關於你的程序產生垃圾的速度等一些信息,這樣它可以明白自己需要多快的進行垃圾收集。如何獲取那些數字可能是有點tricky,但是不管你做什麼,你必須得考慮內存的使用。
如果運行RealtimeThread不是足夠的,在修改完大罵之後,垃圾收集器的停頓將仍然很長或者無法預測。你可能需要使用一個execution context而不是RealtimeThread,例如javax.realtime.NoHeapRealtimeThread.它可以通過使用內存而不是Java堆來獲得可預測的特性,例如immortal memory 和 scoped memory,後面我們將討論他們。獲得可預測性當然需要代價的:典型的情況是犧牲了系統的平均性能。
Java RTS的新特性
讓我們來看一下Java RTS平台中增加了哪些新特性。
*直接內存存取.JAVA RTS 允許對物理內存的直接存取,這與J2ME很像。不要驚奇,JAVA實時系統主要針對的平台就是嵌入式系統。這就意味著現在你可以創建用純JAVA寫的設備驅動了。盡管內存存取並不是一個實時系統的直接要求,許多應用程序還是需要對物理內存做存取。JAVA RST定義了一個新的類,這個類允許程序員對物理內存做字節級別的存取,同時這個類還允許在物理內存中創建對象。有人可能會認為JAVA支持物理內存存取就是放棄了原有的主要的原則-可靠性和安全性,並向C語言又靠近了一步。但這並不是問題的所在,Java通過控制內存邊界和數據內容來實現了一個強大的安全保護措施。
*異步交流。Java RTS 提供了兩種異步交流的形式:異步事件處理和異步傳輸控制。異步事件處理意味這開發者可以計劃對來自JVM外部的事件的反應。異步傳輸控制為一個線程提供了安全的中斷另一個線程的方法。
*High-resolution timing.有很多詳細描述High-resolution timing的方法,包括絕對時間和相對時間。時間的調度和度量能夠具有一個納秒級准確度。
*內存管理。有兩種新的內存區域可以幫助防止由於在實時應用程序中傳統的垃圾回收導致的無法預期的延遲。Immortal memory 和 scoped memory。Immortal memory保存對象而不摧毀他們,直到程序結束。這就意味著在Immortal memory中創建的對象必須像C 程序那樣仔細的分配和管理。scoped memory僅僅被用於當一個進程在一個特定的范圍裡工作的情形。當這個進程離開這個范圍的時候,對象將自動被摧毀。Immortal memory和scoped memory都不會被垃圾收集的,因此可以通過使用它們來避免垃圾收集的影響。Java RTS也為使用內存區域的線程提供了內存分配預算的功能的有限支持。當線程被創建的時候,每個實時線程的最大內存區域消費和最大的分配率可以是指定的。
*實時線程。正如先前所提到的,JAVA RT支持兩種新的線程模型:實時線程(javax.realtime.RealtimeThread) 和非堆實時線程(javax.realtime.NoHeapRealtimeThread).這兩種線程類型都是不能被垃圾收集中斷的。這些線程具有28個級別的優先級,並且和標准的JAVA不同,他們的優先級是嚴格的增強的。實時線程是同步的,並且並不受限於所謂的優先級顛倒(priority inversion),在這種priority inversion情況下,如果一個低優先級的線程擁有一個高優先級的線程所需要的資源,將會阻止了這個高優先級的線程的運行。測試證明Java RTS完全避免了priority inversion,這對於緊急任務來說是很重要的。
它是如何工作的
讓我們先簡短的了解程序員可以多麼輕易的利用這些實時JAVA的新特性的。我們將只考慮新的API中的最有趣的部分:線程和內存。對於其他問題,請參閱real-time Java specification 文檔(PDF).
規范的制訂者們一個主要的目標就是保證RTS編程的簡單性,盡管實時問題是很復雜的;這就意味著操作系統必須盡可能多的完成一些工作,使得程序員只需要完成實時程序設計這個具有挑戰性的工作。
線程
RealtimeThread 類繼承了Java.lang.Thread類。這個類有多個構造函數,開發者能夠調試線程的行為。
public RealtimeThread()
public RealtimeThread(SchedulingParameters scheduling)
public RealtimeThread(SchedulingParameters scheduling,
ReleaseParameters release)
提供給RealtimeThread(還有MemoryParameters )的構造函數的兩個參數ReleaseParameters 和SchedulingParameters使得線程的時間和處理器需求可以通知給系統。RealtimeThread實現了Schedulable接口。關鍵就在於Schedulable對象可以被放置在ImmortalMemory, HeapMemory, ScopedPhysicalMemory, and PhysicalImmortal等類的實例所表示的內存中。
NoHeapRealtimeThread 是RealtimeThread的一個特有的形式。因為NoHeapRealtimeThread實例可以立刻搶占任何已實現的垃圾收集器,它的run()函數中的邏輯不允許分配或引用任何在堆中分配的對象,或者是對在堆中的對象進行操作。例如,如果A和B是immortal memory中的兩個對象。B.p是堆中的一個對象的引用 , A.p和B.p的類型是一致的,那麼NoHeapRealtimeThread是不允許執行與下面類似的代碼的:
A.p = B.p;
B.p = null;
考慮到這些限制,NoHeapRealtimeThread對象必須被放置在一個內存區域中以防止線程可能unexceptionally的控制實例的變量。這就是為什麼NoHeapRealtimeThread的構造函數要求ScopedMemory 或 ImmortalMemory的引用了。當線程啟動後,所有的操作都處於分配的內存區域中。因此,new操作產生的所有的內存分配都處於這個區域中。
內存管理
我們已經發現了一些內存相關的類了。更確切的說,MemoryArea 是所有處理可分配的內存區域的類的抽象的基類,這些內存區域包括ImmortalMemory,物理內存和ScopedMemory。HeapMemory類是一個單體(singleton)對象,它允許其他內存區域中的代碼在JAVA堆中分配內存。這個方法返回一個對HeapMemory單體實例所代表的Java堆的指針。
public static HeapMemory instance()
更有意思的是ImmortalMemory類,這是一個所有線程都共享的內存資源。在ImmortalMemory中分配的對象一直會存活到程序結束的時候,並且絕不會被垃圾收集掉,盡管一些垃圾收集算法可能會要求掃描Immortal Memory
ScopedMemory區域連接到特定內存區域,這是一個處理那些指向具有有限生命周期的內存空間的類
RawMemoryAccess實例把一段范圍的物理內存當作一個固定順序的字節。Accessor方法的完整實現將允許通過偏移量來訪問物理地址的內容,把它解析成byte,short,int或long數據或者這些類型的數組。如果你需要訪問float或是double類型的值,就必須使用RawMemoryFloatAccess類。偏移量是代表high-order 還是 low-order字節取決於RealtimeSystem類中的靜態布爾變量BYTE_ORDER的值。一個raw的內存空間當然不能夠存儲Java對象的引用,因為這樣一種操作是不安全的。RawMemoryAccess類用下面的構造函數來初始化:
public RawMemoryAccess(Java.lang.Object type,
long base, long size)
throws SecurityException,
OffsetOutOfBoundsException,
SizeOutOfBoundsException,
UnsupportedPhysicalMemoryException,
MemoryTypeConflictException,
MemoryInUseException
值得注意的是這個構造函數定義了相當多的可能拋出的異常。Type參數是代表所需內存類型的對象所需要的。它被用於定義初始地址以及控制映射。
結論
實時Java提供了一個更可靠和可預測的調度機制,內存處理方法,各種不同的內存模型,一個可預測性更好的線程和同步模型,異步時間處理,以及high-resolution時間處理。這使得可預測的運行成為傳統的計算性能測試中所有決定中的第一選擇。這就是實時的真谛。
這篇文章只是一個新的概念和Sun的JAVA實現的的綜述。如果你對具體的實現細節很感興趣,你可以在下面的資源中找到很多有用的資料。實時JAVA為JAVA應用程序提供了實時能力,這也使得Java有可能成為第一個可用的商業的實時語言。
資源
* Real-Time Java Platform Programming, by Peter C. Dibble
* Real-Time Specification for Java (PDF)
* Sun J2SE Real-Time Edition
Peter Mikhalenko是Deutsche Bank的一個商務顧問。