首页 / 软件开发 / JAVA / 实时Java,第1部分: 使用Java语言编写实时系统
实时Java,第1部分: 使用Java语言编写实时系统2011-06-22Mark StoodleyMike由于很多重要原因,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 和其他类供应商提供的众多类库,这可能会创建大量的临时对象从而最终将堆填满。