我在一家投资银行工作了许多年 , 我的经验告诉我,在金融软件中出现的绝大多数问题是由于缺少实时支持导致的。许多大型的金融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的一个商务顾问。
查看本文来源