由于许多重要原因,Java语言在实时系统中的使用并不广泛。 这些包括Java语言设计固有的不确定性性能影响,例如动态类加载,以及Java Runtime Environment(JRE)本身,例如垃圾收集器和本机代码编译。 Java实时规范(RTSJ)是一个开放规范,它扩展了Java语言,从而为使用该语言构建实时系统提供了更广泛的机会(请参阅参考资料 )。 实现RTSJ需要在操作系统,JRE和Java类库(JCL)中提供支持。 本文探讨了使用Java语言实现实时系统所面临的挑战,并介绍了解决这些挑战的开发套件和运行时环境。 本系列的后续文章将更深入地介绍本文介绍的概念和技术。
实时 (RT)是一个广义术语,用于描述具有实际时序要求的应用程序。 例如,缓慢的用户界面不能满足一般用户的通用RT要求。 这种类型的应用程序通常被称为软 RT应用程序。 相同的要求可能更明确地表述为“应用程序响应鼠标单击的时间不应超过0.1秒”。 如果不满足要求,那就是软失败:应用程序可以继续运行,并且用户尽管不满意,仍然可以使用它。 相反,必须严格满足实际时序要求的应用程序通常称为硬 RT应用程序。 例如,不得因为任何原因而延迟控制飞机舵的应用程序,因为结果可能是灾难性的。 成为RT应用程序意味着什么在很大程度上取决于应用程序对错过的时序要求形式的故障的容忍度。
RT要求的另一个关键方面是响应时间。 对于编写硬或软RT应用程序的程序员来说,了解响应时间约束至关重要。 满足硬1微秒响应所需的技术与满足硬100毫秒响应所需的技术明显不同。 实际上,要使响应时间低于几十微秒,就需要结合使用定制的硬件和软件,可能没有或只有非常薄的操作系统层。
最后,健壮的RT应用程序的设计人员通常需要一定数量的确定性性能特征,才能设计出满足响应时间要求的应用程序。 不可预测的性能影响大到足以影响系统满足应用程序响应时间要求的能力,这使得很难正确构建该应用程序,甚至无法实现。 大多数RT执行环境的设计人员都致力于降低不确定性的性能影响,以满足最广泛的RT应用程序的响应时间需求。
在通用操作系统上的通用JVM上运行的标准Java应用程序只能希望满足数百毫秒级别的软RT要求。 该语言的几个基本方面负责:线程管理,类加载,即时(JIT)编译器活动和垃圾回收(GC)。 应用程序设计人员可以缓解其中的某些问题,但是只有通过大量工作才能解决。
标准Java不保证线程调度或线程优先级。 必须在定义好的时间内响应事件的应用程序无法确保不会在高优先级线程之前安排另一个低优先级线程。 作为补偿,程序员需要将一个应用程序划分为一组应用程序,然后操作系统可以以不同的优先级运行这些应用程序。 这种划分将增加这些事件的开销,并使事件之间的通信更具挑战性。
符合Java的JVM必须延迟类的加载,直到程序首次引用它为止。 加载类可能要花费可变的时间,具体取决于从中加载类的介质(磁盘或其他)的速度,类的大小以及类加载器本身所引起的开销。 加载类的延迟通常可以高达10毫秒。 如果需要加载数十个或数百个类,则加载时间本身可能会导致严重的延迟,并且可能会导致意外的延迟。 可以使用仔细的应用程序设计来在应用程序启动时加载所有类,但这必须手动完成,因为Java语言规范不允许JVM尽早执行此步骤。
GC对应用程序开发的好处(包括指针安全性,避免泄漏以及使开发人员无需编写自定义内存管理工具)已得到了充分的证明。 但是,对于使用Java语言的RT硬编程人员来说,GC是造成挫败感的另一个原因。 当Java堆耗尽到无法满足分配请求的程度时,垃圾收集就会自动发生。 应用程序本身也可以触发收集。
一方面,对于Java程序员来说,GC是一件了不起的事情。 需要用诸如C和C ++之类的语言显式管理内存而引入的错误是一些最难诊断的问题。 在部署应用程序时证明没有此类错误也是一项基本挑战。 Java编程模型的主要优势之一是JVM(而不是应用程序)执行内存管理,从而减轻了应用程序程序员的负担。
另一方面,传统的垃圾收集器有时会引入较长的延迟,而这对于应用程序程序员而言实际上是无法预测的。 几百毫秒的延迟并不罕见。 在应用程序级别解决此问题的唯一方法是通过创建一组可重复使用的对象来防止GC,从而确保Java堆内存永远不会耗尽。 换句话说,程序员通过自己显式管理内存来丢弃托管内存的好处,从而解决了该问题。 实际上,这种方法通常会失败,因为它阻止程序员使用JDK和其他类供应商提供的许多类库,这些类库可能会创建许多临时对象,这些对象最终会填满堆。
将Java代码编译为本地代码会给类加载带来类似的问题。 大多数现代JVM最初都会解释Java方法,而对于那些经常执行的方法,后来会编译为本地代码。 延迟的编译可导致快速启动,并减少在应用程序执行期间执行的编译量。 但是,使用解释后的代码执行任务并使用编译后的代码执行任务可能会花费大量时间。 对于硬RT应用程序,无法预测何时将进行编译会引入过多的不确定性,从而无法有效地计划应用程序的活动。 与类加载一样,可以通过使用Compiler
类在应用程序启动时以编程方式编译方法来缓解此问题,但是维护这样的方法列表既繁琐又容易出错。
创建RTSJ是为了解决Java语言的某些局限性,从而阻止了其在RT执行环境中的广泛使用。 RTSJ解决了几个有问题的领域,包括调度,内存管理,线程,同步,时间,时钟和异步事件处理。
RT系统需要严格控制线程的调度方式,并确保确定性地调度线程:也就是说,在给定相同条件的情况下,以相同的方式调度线程。 尽管JCL定义了线程优先级的概念,但是不需要传统的JVM来强制执行优先级。 同样,非RT Java实现通常使用具有不可预测的调度顺序的循环抢占式调度方法。 使用RTSJ,RT线程需要真正的优先级和具有优先级继承的固定优先级抢占式调度程序。 这种调度方法可确保最高优先级的活动线程将始终处于执行状态,并且将继续执行,直到其自愿释放CPU或被更高优先级的线程抢占为止。 优先级继承可确保在较高优先级的线程需要较低优先级的线程持有的资源时避免优先级倒置 。 优先级反转是RT系统的一个重要问题,我们将在RTLinux®中对其进行更详细的描述。
尽管某些RT系统可以容忍垃圾收集器导致的延迟,但是在许多情况下,这些延迟是不可接受的。 为了支持不能容忍GC中断的任务,RTSJ定义了不朽和范围内存区域来补充标准Java堆。 如果垃圾收集器需要释放堆中的内存,则这些区域允许任务使用内存而无需阻塞。 分配给不朽内存区域的对象可被所有线程访问,并且永远不会被收集。 由于永生内存从未被收集,因此它是有限的资源,必须谨慎使用。 范围存储区可以在程序员的控制下创建和销毁。 每个范围存储区都分配有最大大小,可用于对象分配。 为了确保对象之间引用的完整性,RTSJ定义了一些规则,这些规则管理一个内存区域(堆,不朽或作用域)中的对象如何引用其他内存区域中的对象。 更多规则定义了范围存储器中的对象何时完成以及何时可以重用存储区域。 由于这些复杂性,不朽内存和作用域内存的建议使用仅限于不能容忍GC暂停的组件。
RTSJ增加了对两个新线程类的支持,它们为执行具有RT行为的任务提供了基础: RealtimeThread
和NoHeapRealtimeThread
(NHRT)。 这些类为优先级,定期行为,可以在超过最后期限时触发的处理程序提供最后期限以及使用堆以外的内存区域提供支持。 NHRT无法访问堆,因此与其他类型的线程不同,NHRT大部分不会被GC中断或抢占。 RT系统通常将具有高优先级的NHRT用于具有最严格的延迟要求的任务,将RealtimeThread
s用于具有可被垃圾收集器容纳的延迟要求的任务,并将常规Java线程用于其他所有任务。 由于NHRT无法访问堆,因此使用这些线程需要高度注意。 例如,甚至必须仔细管理标准JCL中容器类的使用,以使容器类不会在堆上无意间创建临时或内部对象。
必须在RT系统中仔细管理同步,以防止高优先级的线程等待较低优先级的线程。 RTSJ包括优先级继承支持,以在发生同步时对其进行管理,并且它使线程能够通过无等待的读写队列进行通信而无需同步。
RT系统需要比标准Java代码所提供的时钟更高分辨率的时钟。 新的HighResolutionTime
和Clock
类封装了这些时间服务。
RT系统通常管理和响应异步事件。 RTSJ包括对由许多来源触发的异步事件的支持,这些来源包括计时器,操作系统信号,错过的期限以及其他应用程序定义的事件。
实施RTSJ需要基础操作系统以及JRE组件的广泛支持。 的IBM®WebSphere®实时,于2006年8月发布(见相关信息 ),包括完全遵守RTSJ以及旨在改进RT系统运行时的行为和促进工作中的应用设计人员必须做到创建RT系统的多项新技术。 图1显示了WebSphere Real Time组件的简化表示:
WebSphere Real Time基于IBM的跨平台J9技术。 应用于Linux操作系统的开源RT补丁提供了支持RT行为(尤其是RTSJ强制要求的行为)所需的基本RT服务。 显着增强的GC技术支持1毫秒的暂停时间。 JIT编译可用于较软的RT场景,在这些场景中无需进行更高优先级的工作即可进行编译。 还引入了一种新的提前 (AOT)编译技术(图1中未显示),以在不适合JIT编译的系统中提供更高的RT性能。 以下各节介绍了每种技术。 本系列的后续文章将提供有关每种技术如何工作的更多详细信息。
WebSphere Real Time在定制的完全开源的Linux版本上运行。 进行了几处更改以创建RT Java环境。 这些更改提供了完全可抢占的内核,线程中断处理程序,高分辨率计时器,优先级继承和健壮的互斥体。
RT Java线程是通过固定优先级调度(也称为静态优先级调度)和先进先出调度策略实现的。 标准的Linux内核提供了软RT行为,尽管对于高优先级的线程等待先于低优先级的线程等待的时间没有保证的上限,但是时间大约可以是数十微秒。 在RT Linux中,几乎所有内核活动都被抢占,从而减少了优先级较低的线程被抢占所需的时间,并允许更高优先级的线程运行。 其余无法抢占的关键部分简短且确定性地执行。 RT调度延迟已提高了三个数量级,现在大约可以在数十微秒内测量。
几乎所有中断处理程序都转换为在进程上下文中运行的内核线程。 延迟时间更短且更具确定性,因为处理程序成为用户可配置的可调度实体,可以像其他任何进程一样抢占优先级。
高分辨率时间和计时器可提高分辨率和准确性。 RT Java使用这些功能进行高分辨率的睡眠和定时等待。 Linux高分辨率计时器是使用高精度的64位数据类型实现的。 与传统的Linux不同,时间和计时器取决于低分辨率的系统时钟,这限制了计时器事件的粒度,RT Linux使用独立可编程的高分辨率计时器事件,这些事件可以在几微秒内到期。
优先级继承是一种用于避免经典优先级反转问题的技术。 优先级反转的最简单示例之一,如图2的顶部图所示,涉及三个线程:一个高(H),一个中(M)和一个低(L)优先级线程。 想象一下,H和M最初处于Hibernate状态,等待事件被触发,L处于活动状态并保持锁定。 如果H醒来处理事件,它将抢占L并开始执行。 考虑一下如果H在由L持有的锁上阻塞时会发生什么。由于H在L释放锁之前无法取得进展,因此H阻塞并且L重新开始执行。 如果现在由事件触发M,则M将抢占L并执行所需的时间。 这种情况称为优先级反转,因为即使H的优先级高于M,M也会饿死H。
RT Linux通过称为优先级继承 (也称为优先级借出 )的策略防止优先级倒置,如图2的底部示意图所示。 当H在由L持有的锁上阻塞时,H将其优先级赋予L,从而保证没有比H优先级低的任务可以在释放H所需的锁之前抢占L。一旦释放该锁,L的优先级就会还原达到其原始值,以便H可以在不等待L的情况下取得进展。应用程序设计人员仍应努力避免出现以下情况:较高优先级的线程需要较低优先级的线程持有的资源,但是这种优先级继承机制提高了鲁棒性这样可以防止优先级倒置。
Linux pthread互斥锁受快速用户空间互斥锁(称为futexes)支持。 Futex可以优化时间来获得不受争议的锁,而无需依赖内核。 只有有争议的锁才需要内核干预。 健壮的互斥锁解决了在持有锁的应用程序崩溃后正确清理锁的问题。 同样,rt-mutexes将优先级继承协议扩展到健壮的互斥体,这使RT JVM可以通过pthread库依赖于优先级继承行为。
给定提供RT行为基础的RT操作系统(例如RT Linux),可以构建JVM的其他主要部分来展现RT行为。 GC是JVM中不确定性行为的较大来源之一,但是可以通过精心设计和依赖RT Linux的特性来缓解这种不确定性。
GC的不确定性影响会暂停RT应用程序在特定期限内完成任务的能力(请参阅垃圾回收 )。 大多数GC实施都会干扰RT应用程序的延迟目标,以至于只有规模较大且时序要求宽松的任务才可以依靠GC技术。 RTSJ针对此问题的解决方案是通过不朽和作用域内存区域以及NHRT引入程序员管理的内存分配,但是这种解决方案可能会使Java应用程序设计人员感到头疼。
WebSphere Real Time使程序员可以根据需要依赖RTSJ内存区域,但是建议仅将这种方法用于延迟要求非常严格的任务。 对于能够忍受1毫秒量级的GC暂停时间的任务,IBM创建了确定性GC技术,该技术可使程序员从自动内存管理的编程简便性中受益, 并以可预测的性能管理任务。
IBM的确定性GC技术基于两个简单的前提:
考虑到这两个前提来管理GC活动会极大地提高应用程序实现其RT目标的可能性。
WebSphere Real Time使用Metronome GC在JVM中实现确定性的低暂停时间GC行为(请参阅参考资料 )。 Metronome GC使用基于时间的调度方法,该方法将收集器和应用程序交错(在GC中被称为mutator,因为从垃圾收集器的角度来看,该应用程序用于随时间更改活动对象的图)按照固定的时间表。
根据时间而不是分配速率进行调度的原因是,在应用程序执行期间分配通常不均匀。 通过完成GC工作作为对分配的一种征税,可能会导致GC暂停的分布不均匀,从而降低了GC行为的确定性水平。 通过使用基于时间的计划,节拍器GC可以实现一致的,确定的,有限制的暂停时间。 此外,由于不需要对现有代码进行语言扩展或修改,常规Java应用程序可以透明地使用Metronome,并受益于其确定性特征。
节拍器将时间分为一系列离散的量子,大约500微秒,但长度不超过1毫秒,用于GC工作或应用工作。 尽管量子非常短,但是如果几个量子专用于GC工作,则应用程序仍会经历更长的暂停时间,这可能会危及RT截止日期。 为了更好地支持RT期限,Metronome分配了专用于GC工作的量子,因此应用程序应获得最少的时间百分比。 该百分比称为利用率 ,是用户提供的参数。 在任何时间间隔内,分配给应用程序的量子数量应不少于指定的利用率。 默认情况下,利用率为70%:在任何10毫秒的时间窗口中,至少7毫秒将专门用于应用程序。
用户可以在程序启动时设置利用率。 图3显示了较长时间的应用程序利用率示例。 请注意,与垃圾收集器处于活动状态的时间量子相对应的周期性骤降。 在图3所示的整个时间窗口中,应用程序利用率保持等于或高于指定的70%(0.7)。
图4展示了节拍器技术在GC暂停时间方面的确定性。 只有一小部分的暂停时间超过500微秒。
为了使单个GC暂停时间较短,Metronome使用堆中的写屏障以及相关的元结构来跟踪活动对象和可能死掉的对象。 跟踪活动对象需要一系列GC数量来确定哪些对象应保持活动状态以及应回收哪些对象。 由于此跟踪工作与程序执行交织在一起,因此GC可能无法跟踪应用程序可以通过执行加载和存储“隐藏”的某些对象。
隐藏活动对象不一定是恶意应用程序代码的结果。 这是更常见的原因,因为应用程序不了解垃圾收集器的活动。 为确保收集器不会遗漏任何对象,GC和VM通过跟踪对象之间的链接来协作,这些链接是通过应用程序执行的存储操作创建和断开的。 在应用程序执行存储操作之前执行的写屏障会执行此跟踪。 如果此存储可能导致隐藏活动对象,则写屏障的目的仅仅是记录对象如何链接在一起的更改。 这些写障碍代表了性能和内存占用开销,它们平衡了确定性行为的需求。
对于许多GC策略,大对象的分配可能会很麻烦。 在许多情况下,堆过于分散,无法容纳单个大对象(例如数组)。 因此,它必须引起长时间的停顿才能对堆进行碎片整理或压缩,以将许多较小的可用内存区域合并为较大的可用内存区域,以满足较大的分配请求。 节拍器对数组使用了新的两级对象模型,称为arraylets 。 Arraylet将大型阵列分解为较小的部分,以使大型阵列分配更容易满足,而无需对堆进行碎片整理。 arraylet对象的第一层称为spine ,包含指向该数组较小的块(称为leaves)的指针的列表。 每个叶子的大小相同,这简化了查找数组中任何特定元素的计算,并且使收集器更容易找到合适的可用空间来分配每个叶子。 将数组分解为较小的不连续块可以使数组分配到堆中通常会出现的许多较小的可用区域中,而无需进行压缩。
与传统的STW垃圾收集器实现不同,传统的STW垃圾收集器实现具有GC周期的概念来表示垃圾收集的开始和结束,而Metronome在应用程序的整个生命周期中将GC作为连续过程执行。 在应用程序的生命周期内保证了应用程序的利用率,在不需要大量GC工作的情况下,其利用率可能高于最低利用率。 当收集器找到可用内存以返回到应用程序时,可用内存会上下波动。
大多数现代JVM结合使用解释和编译代码执行。 为了消除解释的高性能成本,JIT编译器选择了频繁执行的代码以直接转换为CPU的本机指令。 Java语言的动态特性通常会导致该编译器在程序执行时运行,而不是在程序运行之前发生的步骤运行(例如C ++或Fortran之类的语言)。 JIT编译器可以选择要编译的代码,因此可以通过提高代码的性能来弥补编译所花费的时间。 除了这种动态编译行为之外,传统的JIT编译器还采用了各种推测性优化,这些优化利用了正在运行的程序的动态特性,这些特性可能在一个特定程序执行期间的某一时刻是正确的,但在执行期间可能不会保持正确。 如果以后关于此特性的假设变为假,则可以“取消”这种优化。
在传统的非RT环境中,在程序执行时编译代码效果很好,因为编译器的操作对应用程序的性能几乎是透明的。 但是,在RT环境中,JIT编译器引入了不可预测的运行时行为,这对最坏情况的执行时间分析造成了严重破坏。 但是,在这种环境下,编译后代码的性能优势仍然很重要,因为它可以使更复杂的任务在较短的时间内完成。
WebSphere Real Time引入了两个解决方案,以在不同的权衡点上平衡这两个需求。 第一个解决方案是采用以较低的非RT优先级运行的JIT编译器,该编译器已被修改为执行较少的主动式投机优化。 以非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代码时Java代码如何在WebSphere Real Time中执行:
从图5的左上方开始,开发人员像在任何Java开发项目中一样,将Java源代码编译为类文件。 将类文件捆绑到JAR文件中,然后使用jxeinajar
工具对其进行AOT编译。 该工具可以编译JAR文件中所有类中的所有方法,也可以基于程序的基于JIT的示例执行生成的输出有选择地编译某些方法,该示例基于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代码的JXE通常比保存类文件的JAR文件大得多,因为本机代码通常不如存储在类文件中的字节码那么密集。 本机代码执行还需要各种补充数据,以描述如何将代码绑定到JVM中以及如何捕获异常,例如,以便可以执行代码。 第二个缺点是,尽管AOT编译的代码比解释的代码要快,但它比JIT的编译的代码要慢得多。 Finally, the time to transition between an interpreted method and a compiled method, or vice versa, is higher than the time to call an interpreted method from another interpreted method or to call a compiled method from a compiled method. In a JVM with an active JIT compiler, this cost is eventually eliminated by compiling "around the edges" of the compiled code until the number of transitions is too small to impact performance. In a JVM with AOT-compiled code but no JIT compiler, the number of transitions is determined by the set of methods that were compiled into the JXEs. For this reason, we typically recommend AOT compiling the entire application as well as the Java library classes on which the application depends. Expanding the number of compiled methods, as we mentioned above, has a footprint impact although the benefit to performance is usually more critical than the footprint increase.
The reason AOT code is generally slower than JIT code is because of the nature of the Java language itself. The Java language requires that classes be resolved the first time the executing program references them. By compiling before the program executes, the AOT compiler must be conservative about classes, fields, and methods referenced by the code it compiles. AOT-compiled code is often slower than JIT-compiled code because the JIT has the advantage that it is performing compilation after the executing program has resolved many of these references. However, the JIT compiler must also carefully balance the time it takes to compile a program because that time adds to the program's execution time. For this reason, JIT compilers do not compile all code with the same degree of optimization. The AOT compiler does not have this limitation, so it can afford to apply more-aggressive compilation techniques that sometimes yield better performance than JIT-compiled code. Moreover, more methods can be AOT compiled than a JIT compiler might decide to compile, which can also result in better performance with AOT compilation than JIT compilation. Nonetheless, the common case is that AOT-compiled code is slower than JIT-compiled code.
To avoid nondeterministic performance effects, neither the JIT compiler nor the AOT compiler provided in WebSphere Real Time applies the aggressively speculative optimizations generally applied by modern JIT compilers. These optimizations are generally performed because they can produce substantial performance improvements, but they are not appropriate in a RT system. Furthermore, supporting the various aspects of the RTSJ and the Metronome garbage collector introduces some overheads into compiled code that traditional compilers need not perform. For these reasons, code compiled for RT environments is typically slower than the code compiled for non-RT environments.
More can be done to make an RT Java environment faster, in terms of both predictable performance and raw throughput. We see two key areas of advancement that must occur for the Java language to succeed in the RT application space:
Many features of WebSphere Real Time are useful to programmers targeting a traditional operating system. Incremental GC and priority-based threads would clearly be useful in many applications, even if hard RT guarantees could not be met and only soft RT performance could be provided. An application server providing predictable performance without unpredictable GC delays, for example, is an attractive idea to many developers. Similarly, enabling applications to run high-priority Java health-monitoring threads with reasonable scheduling targets would simplify Java server development.
Simply bringing the advantages of using the Java language to the process of creating RT systems is a tremendous benefit to developers. But there's always room for improvement, and we are constantly evaluating new features that could simplify RT programming even further. You can go to our IBM alphaWorks site to try out our expedited real-time threads research technology that lets developers manage extremely high-frequency events with very little tolerance for variance or delay (see Related topics ). The tooling achieves highly deterministic behaviour by preloading, preinitializing, and precompiling the code to handle events and then running the code independently of the garbage collector with fewer and less onerous restrictions than the NHRTs in the RTSJ. You'll also find tooling called TuningFork, which traces paths from the operating system through the JVM and into applications, making it easier to perform detailed performance analysis.
翻译自: https://www.ibm.com/developerworks/java/library/j-rtj1/index.html