[转载]实时 Java,第 4 部分: 实时垃圾收集

实时 Java,第 4 部分: 实时垃圾收集


2007 年 6 月 04 日

传统垃圾收集(GC)中的不确定性暂停使 Java™ 技术无法提供适合实时(RT)开发的环境。Metronome GC —— IBM® WebSphere® Real Time 的一部分 —— 提供了确定性的 GC 行为,如果与其他特性结合使用,就能使开发人员用 Java 语言编写硬 RT 应用程序。作者介绍了 Metronome 用于确定性 GC 的方法、开发 Metronome 的过程中涉及的技术问题,以及用于调优 GC 的工具和设备。

实时系统和垃圾收集

实 时(RT)应用程序开发与通用应用程序开发的差异在于前者对部分运行时行为强加了时间限制。此类限制通常是对应用程序的某些部分实施的,比如中断处理程 序,其响应中断的代码必须在给定的时间范围内完成工作。对于硬 RT 系统,比如心脏监测器或国防系统,如果这类系统的运行超出时限,可以看作是整个系统的灾难性失败。而对于软 RT 系统,超出时限可能会有些不利影响 —— 比如 GUI 不能显示其监控流的所有结果 —— 但是不会导致系统失败。

在 Java 应用程序中,Java 虚拟机(JVM)负责优化运行时行为、管理对象堆以及接合操作系统和硬件。虽然语言和平台之间的这个管理层简化了软件开发,但同时也给程序带来了一定数量 的开销。GC 就是一个这样的例子,它通常会导致应用程序中的不确定性暂停。暂停的频率和时长都不可预测,使得 Java 语言在传统上并不适合开发 RT 应用程序。一些基于 Java 实时规范(RTSJ)的现有解决方案使开发人员能够避开 Java 技术的不确定性方面,但是需要对现有的编程模型做些更改。

Metronome 是一种确定性的垃圾收集器,为标准的 Java 应用程序提供有限制的低暂停时间和指定的应用程序利用率。 有限制的暂停时间的减少源于收集方法的增加和细致的工程决断,包括对 VM 的基本更改。利用率是指应用程序所能够运行的特定时间窗中的时间百分比,剩余时间则用于 GC。Metronome 让用户能够指定应用程序的利用率级别。通过与 RTSJ 结合使用,Metronome 使开发人员能够在时间窗很小的情况下构建具有确定的低暂停时间和无暂停的软件。本文解释了 RT 应用程序的传统 GC 的限制,详述了 Metronome 的方法,并且为使用 Metronome 开发硬 RT 应用程序提供了一些工具和指导。


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


传统 GC

传统 GC 实现使用 stop-the-world (STW) 方法来恢复堆内存。应用程序一直运行,直至耗尽堆的可用内存,此时 GC 停止所有的应用程序代码、执行垃圾收集,然后让应用程序继续运行。

图 1 演示了用于 GC 活动的传统的 STW 暂停,这些暂停在频率和持续时间方面通常都不可预测。传统的 GC 是不确定的,因为恢复内存所需执行工作的数量取决于应用程序所使用对象的总的数量和大小、这些对象之间的相互连接,以及为释放足够的堆内存以满足未来分配 所需完成的工作的多少。


图 1. 传统 GC 暂停
传统 GC 暂停

传统 GC 非确定性的原因

考察一下 GC 的基本组件,就不难理解 GC 时间没有限制并且不可预测的原因了。GC 暂停通常包括两个独立阶段:标记 阶段和清理 阶段。虽然很多实现和方法可以结合或修改这两个阶段的含义,或通过其他手段(如压缩或减少堆中的碎片)来增强 GC,或使某些阶段的操作与应用程序的运行并发执行,这两个概念是传统 GC 的技术基线。

标记阶段负责跟踪应用程序可见的所有对象并将它们标记 为活的,以免回收它们的存储。这个跟踪以根集 开始,它由一些内部结构组成,比如线程栈和对象的全局引用。跟踪然后遍历引用链直至标记完根集中所有(直接或间接)可获得的对象。标记阶段最后也没有标记的对象是应用程序不可获得的对象(死对象), 因为不存在从根集经过任何引用序列找到这些对象的路径。标记阶段的长度不可预测,原因是应用程序中活对象的数目在任何特定时间都不可预测,并且遍历所有引 用以便找到系统中所有活对象的耗费也不可预测。一个运行稳定的系统中的 oracle 可以根据以前的计时特征来预测时间需求,但是这些预测的精确性又是不确定性的另一个来源。

清理阶段负责在标记完成后考察堆并回收死对象的存储,将其放回堆的自由存储中,使那些存储可用于分配。与标 志阶段类似,将死对象清理回自由内存池的耗费也不能完全预测。虽然系统中活对象的数目和大小可从标记阶段获得,但是对它们在堆中的位置及其对于自由内存池 的适宜性进行分析所需的工作却不可预测。

传统 GC 对于 RT 应用程序的适宜性

RT 应用程序必须能够响应具有确定时间间隔的实际刺激。传统 GC 无法满足这个需求,因为应用程序必须暂停以便 GC 可以回收所有未使用的内存。回收所花费的时间没有限制并受波动的影响。此外,GC 中断应用程序的时机在传统上不可预测。应用程序暂停的持续时间被称作暂停时间,因为这段时间暂停了应用程序进程使 GC 可以回收自由空间。RT 应用程序要求低暂停时间,因为那通常表示应用程序响应具有较高的计时限制。


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


Metronome GC

Metronome 的方法用于将执行 GC 循环的时间划分为一系列的增量,称作量子。为此,每个阶段通过一系列不连续的步骤来完成其全部工作,允许收集器执行以下操作:

  1. 抢占应用程序一小段确定的时间。
  2. 执行收集操作。
  3. 让应用程序恢复运行。

这个顺序与传统模型形成了很好的对比,在传统模型中,应用程序在某个不可预测的时间暂停,GC 运行一段没有限制的时间后完成,然后 GC 停止,让应用程序恢复运行。

虽 然将 STW GC 循环分解为短暂的有限制的暂停有助于减少 GC 的影响,但这对 RT 应用程序来说还不够。为了使 RT 应用程序满足其时限要求,任何特定时间段中都必须有足够的部分可供应用程序使用;否则,就会发生需求冲突,应用程序也会失败。例如,可假定一个 GC 暂停被限制为 1 毫秒的场景:在每 1 毫秒的 GC 暂停之间,应用程序只能运行 0.1 毫秒,则程序执行几乎没有进展,甚至不太复杂的 RT 系统也可能失败,因为它们缺少时间执行处理。实际上,足够接近的短暂停时间与完整的 STW GC 并无二致。

图 2 演示了 GC 运行多数时间但仍然保留 1 毫秒的暂停时间:


图 2. 暂停时间短但是应用程序时间更短
暂停时间短但是应用程序时间更短

利用率

除 了有限制的暂停时间外,还需要另外一种测量方法来确定分配给应用程序和 GC 的时间百分比。我们将应用程序利用率定义为:分配给特定时间窗内的应用程序的时间百分比,在这段时间内应用程序连续地执行完整个运行过程。 Metronome 保证应用程序可以获得一定百分比的处理时间。剩余时间的使用由 GC 决定:可以分配给应用程序也可以由 GC 使用。短暂停时间可以保证比传统收集器分解得更细的利用率。因为用于测量利用率的时间间隔趋近于零,所以应用程序的预期利用率是 0% 或 100%,原因是这个时间间隔低于 GC 量的大小。对滑动窗口大小的度量必须严格保证利用率。Metronome 在 10 毫秒的时间窗内使用 500 微秒的时间量,默认的利用率目标为 70%。

图 3 演示一个划分为多个 500 微秒时间片的 GC 循环,该循环在 10 毫秒的时间窗内具有 70% 的利用率:


图 3. 滑动窗口利用率
滑动窗口利用率

在 图 3 中,每个时间片表示运行 GC 或应用程序的一个时间量。时间片下面的各栏表示滑动窗口。每个滑动窗口具有最多 6 个 GC 时间量和至少 14 个应用程序时间量。每个 GC 时间量后接至少 1 个应用程序时间量,即使通过连续的 GC 时间量来保持目标利用率也是如此。这就保证了将应用程序暂停时间限制为 1 个时间量长度。但是,如果指定的目标利用率低于 50%,则会产生一些连续的 GC 时间量使 GC 能够满足分配。

图 4 和图 5 演示了一个典型的应用程序利用率场景。在图 4 中,利用率降为 70% 的区域表示正在进行 GC 循环的区域。注意,如果 GC 是不活动的,则应用程序的利用率为 100%。


图 4. 总利用率
总利用率

图 5 演示了图 4 的一个 GC 循环片段:


图 5. GC 循环利用率
GC 循环利用率

图 5 的 A 段是一个梯型图,其中下降的部分对应于 GC 时间量,而平缓的部分对应于应用程序时间量。梯型表示 GC 通过与应用程序交错实现低暂停时间,从而产生目标利用率的梯状下降。组成 B 段的应用程序活动只保持所有滑动窗口的利用率目标。利用率模式只在模式开始时显示 GC 活动,这一点很常见。原因在于只要得到允许,GC 就会运行(保持暂停时间和利用率),而这通常意味着它会在模式开始时耗尽分得的时间并允许应用程序在时间窗的剩余部分恢复执行。C 段表示利用率接近目标利用率时的 GC 活动。上升的部分表示应用程序时间量,而下降的部分表示 GC 时间量。造成此段呈锯齿状的原因同样是因为 GC 和应用程序交错执行以便保持低暂停时间。D 段表示 GC 循环完成前的部分。此段呈上升趋势表示 GC 不再运行而应用程序将重新获得 100% 的利用率。

在 Metronome 中,用户可以指定目标利用率;本文的 调整 Metronome 一节提供了与此相关的更多信息。

使用 Metronome 运行应用程序

Metronome 的设计目的是为现有的应用程序提供 RT 行为。不需要修改用户代码。期望的堆大小和目标利用率必须针对应用程序进行调整,使目标利用率保持期望的应用程序吞吐量,同时使 GC 能够满足分配。用户应该按照希望维持的最大负载运行应用程序,以便保证 RT 特征和充足的应用程序吞吐量。本文的 调整 Metronome 一节说明了吞吐量或利用率不足时可以执行哪些操作。在某些情形下,Metronome 的短暂停时间保证不能满足应用程序的 RT 特征。此时,您可以使用 RTSJ 来避免 GC 导致的暂停时间。

Java 实时规范

RTSJ 是 “一种使 Java 程序能够用于实时应用程序的 Java 平台规范”。Metronome 必须意识到 RTSJ 的某些方面 —— 尤其是 RealtimeThread(RT 线程)、NoHeapRealtimeThread(NHRT)和永久内存。 除了别的特征外,RT 线程是以高于普通 Java 线程的优先级运行的 Java 线程。NHRT 是不能包含堆对象引用的 RT 线程。换言之,NHRT 能够访问的对象不能引用服从 GC 的对象。作为对这种妥协的交换,GC 不会阻止 NHRT 的调度,即使在 GC 循环期间也是如此。这意味着 NHRT 不会导致任何暂停时间。永久内存提供了一个不服从 GC 的内存空间;即,NHRT 可以引用永久对象。这些只是 RTSJ 的一些方面,有关完整规范的链接,请参阅 参考资料


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


确定性 GC 相关的技术问题

Metronome 使用 J9 虚拟机中的几个关键方法来实现确定的暂停时间,同时保证 GC 的安全性。这些方法包括 arraylet、基于时间的垃圾收集器调度、用于跟踪活对象的根结构处理、协调 J9 虚拟机和 GC 以保证能够找到所有的活对象,以及用于暂停 J9 虚拟机来提供 GC 时间量的机制。

Arraylet

虽 然 Metronome 通过将收集过程分解为步进的工作单元实现了确定的暂停时间,但是在某些情形下分配可能导致 GC 中出现 hiccup。大对象的分配就是一个这样的例子。对大多数收集器实现而言,分配子系统持有一个自由堆内存池,应用程序通过分配对象使用该池,然后由收集器 通过清理来补充该池。第一次收集后,自由堆内存主要是一些曾经的活对象(现在已死)的结果。因为没有关于这些对象如何死去或何时死去的可预测模式,所以得 到的堆上的自由内存是大小不一的碎片集合,即使会出现相邻死对象合并的情况。此外,每个收集循环会返回一个不同的自由块模式。结果,如果没有足够大的自由 内存块能够满足请求的需要,则分配一个很大的对象就会失败。这些大对象通常是数组;标准对象一般不会多于几十个字段,在大多数 JVM 中常常占用不到 2K 的空间。

为了缓解碎片问题,一些收集器针对其收集循环实现一个压缩或碎片整理阶段。清理完成后,如果分 配请求无法满足,则系统将尝试移动堆中现有的活对象以便将两个或更多的自由块合并成一个更大的块。这个阶段有时作为一个随需应变的特性来实现,被嵌入到收 集器的结构(例如半空间收集器)中,或以一种增量的形式来实现。每个这样的系统都有自己的平衡方法,但一般说来压缩阶段在时间和工作上都耗费颇多。

WebSphere Real Time 中当前版本的 Metronome 没有实现压缩系统。为使碎片不成为一个问题,Metronome 使用 arraylet 将标准的线性表示分解为若干个不连续的小块,可以对这些小块进行彼此独立的分配。

图 6 演示了数组对象作为 spine(它是可由堆上的其他对象引用的中心对象和惟一实体)和一系列的 arraylet 叶子(包含有实际的数组内容)出现:


图 6. Arraylet
Arraylet

arraylet 叶子不由其他的堆对象引用,并且可能在堆中的任何位置以任意顺序分布。这些叶子具有固定的大小,允许对元素的位置进行简单的计算,这是一个附加的迂回。如 图 6 所示,spine 中由于内部碎片导致的内存使用开销已经通过将叶子的所有 trailing 数据包含到 spine 中而得到优化。

注意,这种格式意味着数组 spine 可能成长到无限制的大小,但是在现有的系统中还没有发现这是一个问题。

调度 GC 时间量

为了给 GC 调度确定性暂停,Metronome 使用了以下两个不同的线程来完成一致性调度和短暂连续的暂停时间:

  • alarm 线程。 为了确定地调度 GC 时间量,Metronome 使用 alarm 线程来用作心跳机制。alarm 线程具有很高的优先级(比系统中所有其他的 JVM 线程的优先级都要高),它的唤醒速度与 GC 量子时间段相同(Metronome 中为 500 微秒),并负责决定是否应该调度某个 GC 时间量。如果应该调度,则 alarm 线程必须暂停运行 JVM 并唤醒 GC 线程。alarm 线程只在一个很短的时间段内处于活动状态(通常低于 10 微秒)并随应用程序静默的执行。

  • GC 线程。GC 线程在一个 GC 时间量期间执行实际工作。GC 线程必须首先完成对 alarm 线程启动的 JVM 的暂停。然后才能在剩余的时间内执行 GC 工作,在时间量临近结束时将自身调回休眠状态并恢复 JVM 的运行。如果 GC 线程无法在时间量结束前完成预定的任务项目,那么也可以抢占性地进入休眠。对于 RTSJ 而言,GC 线程的优先级比除 NHRT 之外的所有 RT 线程的优先级都要高。

协作暂停机制

虽然 Metronome 使用一系列小的、步进式的暂停来完成一个 GC 循环,但是它仍然必须以 STW 方式为每个时间量暂停 JVM。对于每个这样的 STW 暂停,Metronome 在 J9 虚拟机中使用协作暂停机制。 这个机制不依赖任何特殊的本地线程功能来暂停线程。作为替代,它使用了一个异步形式的消息传递系统来通知 Java 线程:必须释放对内部 JVM 结构(包括堆)的访问并进入休眠,直至被告知恢复处理。J9 虚拟机中的 Java 线程周期性地检查是否发出了暂停请求,如果已经发出,则它们将执行以下步骤:

  1. 释放所有使用的内部 JVM 结构。
  2. 将所有使用的对象引用存储在良好描述的位置。
  3. 告知中央 JVM 暂停机制已经到达安全点
  4. 休眠并等待相应的恢复

一 旦恢复,线程将重新读取对象指针并重新获取其先前占用的 JVM 相关结构。释放 JVM 结构的操作让 GC 线程以一种安全的形式处理这些结构;对部分更新的结构进行读和写操作可能导致不可预测的行为和冲突。通过存储和重新加载对象指针,线程给 GC 在 GC 时间量期间提供了更新对象指针的机会,如果对象作为任何类似压缩的操作的一部分移动时有此更新必要。

因 为暂停机制与 Java 线程协作,所以每个线程中的周期性检查应该用尽可能小的时间间隔分开,这一点非常重要。这一任务由 JVM 和即时(JIT)编译器负责完成。虽然检查暂停请求会带来系统开销,但是可以根据 GC 的需要很好地定义一些结构(比如栈),让它精确地确定栈中的值是否为对象的指针。

这种暂停机制仅用于当前参与 JVM 相关活动的线程;非 Java 线程或 Java 本地接口(JNI)代码外并且不使用 JNI API 的 Java 线程不服从暂停。如果这些线程参与了任何 JVM 活动(比如连接到 JVM 或调用 JNI API),则它们将协作性的进行暂停,直至完成 GC 时间量。这一点很重要,原因在于它使 Java 处理相关的线程继续得到调度。虽然线程的优先级会得到考虑,但是在这些其他的线程中对系统进行任何可见形式的干扰都会影响 GC 的确定性。

写入屏障

全 面的 STW 收集器具有以下优点:可以跟踪对象引用和 JVM 内部结构,应用程序不会干扰对象图中的链接。通过将 GC 循环分解为一系列小的 STW 阶段并与应用程序交错执行,Metronome 确实带来了跟踪系统中活对象的潜在问题。因为应用程序在处理对象后可能修改对象的引用,使收集器无法察觉未处理对象。图 7 演示了隐藏对象的问题:


图 7. 隐藏对象问题
隐藏对象问题

假 定图 7 第 I 段所描述的堆中存在一个对象图。Metronome 收集器处于活动状态并在此时间量中被分配执行跟踪工作。在其分配的时间段中,它的作用是在时间用完之前跟踪根对象及其引用的对象,并且需要将 JVM 调回 II 段。在应用程序的运行期间,对象之间的引用会发生变化,因此对象 A 现在指向一个未处理对象,该对象不再被 III 段中的任何其他位置引用。然后 GC 被调回并在其他的时间量中继续处理,漏掉这个隐藏 对象指针。结果是,在把未标记对象返回到自由列表的 GC 的清理阶段,将会回收一个活对象,产生一个悬空 指针,导致不正确的行为或者甚至是 JVM 或 GC 中的破坏。

为了防止出现此类错误,JVM 和 Metronome 必须协作跟踪堆和 JVM 的更改,使 GC 将所有的相关对象保持为活的。这项任务通过写入屏障 来完成,它将跟踪所有的对象写操作并记录对象间引用的创建和销毁,使收集器可以跟踪潜在隐藏的活对象。Metronome 所使用的屏障类型称为初始快照(snapshot at the beginning,SATB)屏障。它在收集循环开始时在概念上记录堆的状态并保留当时的和当前循环中分配的所有活对象。具体的解决方案涉及一个 Yuasa 类型的屏障(请参阅 参考资料),其中将会记录任何字段存储中的重写值并将其视为具有相关的根引用。在重写启用活对象设置保护和处理之前保留槽的原始值。

内部 JVM 结构也需要此类屏障处理,包括 JNI 全局引用列表结构。因为应用程序可以向此列表中添加对象和从中删除对象,所以可以使用屏障来跟踪删除的对象(避免类似于字段重写的隐藏对象问题)和添加的对象(消除重新扫描结构的需求)。

根扫描和根处理

为了开始跟踪活对象,垃圾收集器从一组 中获得的初始对象开始。根是 JVM 中的结构,表示了应用程序显式(如 JNI 全局引用)创建或隐式(如栈)创建的对象的硬引用。根结构被作为收集器中标记阶段初始功能的一部分进行扫描。

大多数根在执行期间可以根据其对象引用进行延展。出于这个原因,必须跟踪对它们的引用设置的更改,如 写入屏障 一节所述。但是,某些结构(如栈)不能提供对未造成严重性能影响的 push 和 pop 的跟踪。因此,对扫描栈作出了某些限制和更改,使 Metronome 能够适合 Yuasa 形式的屏障:

  • 栈的原子扫描。单 独线程栈必须自动进行扫描,或在单个时间量内扫描。这样做的原因在于,执行期间线程可以从其栈中弹出任意数量的引用 —— 执行过程中可能存储在其他位置的引用。栈扫描过程中的暂停可能导致丢失对存储的跟踪或在两部分扫描之间错过,在堆中产生一个悬空指针。应用程序开发人员应 该意识到栈应自动进行扫描并且应避免在 RT 应用程序中使用很深的栈。

  • 模糊屏障。虽 然栈必须自动进行扫描,但是如果在单个时间量期间所有栈都被扫描,则可能难以保持确定性。GC 和 JVM 可以在扫描 Java 栈的同时交错执行。这可能导致通过一系列加载和存储将对象从一个线程移动到另一个线程。为了避免丢失对象的引用,GC 期间未被扫描过的线程让屏障跟踪重写值和存储的值。跟踪存储的对象,应将其存储在已经处理过的对象中并从栈中弹出,通过写入屏障保持可获取性。

blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


调整 Metronome

了 解堆大小和应用程序利用率方面的关系非常重要。虽然高目标的利用率对于实现最佳应用程序吞吐量很有帮助,但是 GC 必须能够跟上应用程序的分配率。如果目标利用率和分配率都很高,则应用程序可能耗尽内存,强迫 GC 连续地运行并且在多数情况下使利用率降低到 0%。这种降低带来了大量的暂停时间,通常对 RT 应用程序来说不可接收。如果遇到这种情形,必须作出选择来降低目标利用率,以便提供更多的 GC 时间,增加堆大小以支持更多的应用程序,或将此二者结合使用。某些情形可能需要使用内存以维持确定的目标利用率,因此在性能开销上降低目标利用率是惟一选 择。

图 8 演示了一种典型的堆大小和应用程序利用率之间的平衡。更高的利用率百分比需要更大的堆,因为有些堆在低利用率的应用程序中允许运行,而在 GC 中则不允许运行。


图 8. 堆大小和利用率的对比
堆大小和利用率的对比

利用率和堆大小之间的关系跟应用程序有很大关系,达到一个大致平衡需要使用应用程序和 VM 参数反复实验。

冗余 GC

冗余 GC 是一种记录 GC 活动并将其输出到一个文件或屏幕中的工具。您可以使用它来确定参数(堆大小、目标利用率、窗口大小和时间量)是否支持应用程序运行。清单 1 演示了一个冗余输出的例子:


清单 1. 冗余 GC 示例
                
<?xml version="1.0" ?>




































每个 Verbose GC 事件都包含在 标记中。有多种可用的事件类型,但是清单 1 中给出了最常见的几种。synchgc 类型表示同步 GC,它是从头到尾连续运行的 GC 循环;即,不与应用程序交错运行。发生这种情况有以下两个原因:

  • System.gc() 由应用程序调用。
  • 堆被注满,应用程序分配内存失败。

同步 GC 的原因包含在 标记中,其中 system garbage collect 用于第一种情形而 out of memory 用于第二种情形。第一种情形在应用程序的可维持性方面没有使用特定的参数。但是,在很多情况下从用户应用程序调用 System.gc() 会导致应用程序利用率降至 0% 并导致较长的暂停时间;因此应该避免这种情况。但是如果因为第二种情况发生同步 GC —— 内存不足错误 —— 则意味着 GC 不能跟上应用程序分配。因此您应该考虑增加堆或降低应用程序利用率目标以避免出现同步 GC。

trigger GC 事件类型对应 GC 循环的开始点和结束点。它们可用于对 heartbeat GC 事件进行分批。heartbeat GC 事件类型将多个 GC 时间量的信息整合到一个总括的冗余事件中。注意,这与 alarm 线程心跳无关。quantumcount 属性对应 heartbeat GC 中整合的 GC 时间量。 标记表示关于 heartbeat GC 中整合的 GC 时间量的计时信息。 和 标记包含关于 heartbeat GC 中整合的时间量结尾的自由内存的信息。 标记包含关于时间量开始时 GC 线程优先级的信息。

时间量值指的是应用程序所见的暂停时间。普通的时间量接近 500 微秒,而且必须对最大的时间量进行监控以保证它们能够为 RT 应用程序提供可接受的暂停时间。长暂停时间可能源于 GC 被系统中的其他进程抢占,使其不能完成其时间量而且不能让应用程序恢复运行,或者是因为滥用了系统中的某些根结构并成长到了不可管理的大小(见 使用 Metronome 时的注意事项 一节)。

永久内存是 RTSJ 所需要的并且不服从 GC 的资源。出于这个原因,常常会发现冗余 GC 记录中的永久自由内存下降并且不会恢复。它用于诸如字符串常量和类之类的对象。需要注意程序的行为并适当地调整永久内存的大小。

应该监控堆的使用以保证总趋势保持稳定。堆自由空间呈下降趋势表明可能存在应用程序导致的泄漏。导致泄漏的原因很多,包括越来越多的散列表、不确定保存的大型资源对象和未清理的全局 JNI 引用。

图 9 和图 10 演示了自由堆空间中的稳定和递减的趋势。注意,容易实现本地的最小和最大空间,因为自由空间仅在 GC 循环期间增加并且在应用程序处于活动时和进行分配时相应减少。


图 9. 稳定自由堆
稳定自由堆

图 10. 递减自由堆
递减自由堆

标记的 interval 属性指的是从上次输出相同类型的冗余 GC 事件起所经过的时间。对于 heartbeat 事件类型,它用来表示从 trigger start 事件起所经过的时间(如果它是当前 GC 循环的第一次心跳)。

Tuning Fork

Tuning Fork 是一种独立的工具,用于调优 Metronome 以便更好地适合用户应用程序。Tuning Fork 让用户检查 GC 活动的很多细节,方法是在执行活动后通过跟踪日志检查或在运行时通过套接字检查。Metronome 构建时考虑了 Tuning Fork 并记录了很多可在 Tuning Fork 应用程序中检查的事件。例如,它显示了时间上的应用程序利用率并检查了用于各个 GC 阶段的时间。

图 11 演示了 Tuning Fork 所生成的 GC 性能概要图,包括目标利用率、堆内存使用和应用程序利用率:


图 11. Tuning Fork 性能概要
Tuning Fork 性能概要

blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


使用 Metronome 时的注意事项

Metronome 竭力为 GC 提供短暂确定的暂停,在应用程序代码中和底层平台中出现了一些可能影响这些结果的情形,有时会导致暂停时间异常。使用标准 JDK 收集器带来的 GC 行为更改在此也会出现。

RTSJ 规定 GC 不能处理永久内存。因为类位于永久内存中,所以它们不服从 GC 并且因此不能被卸载。希望使用大量类的应用程序需要适当地调整永久空间,要卸载类的应用程序需要在 WebSphere Real Time 中调整其编程模型。

Metronome 中的 GC 工作是基于时间的,而对硬件时钟的任何更改都可能导致难于诊断的问题。使系统时间与 Network Time Protocol (NTP) 服务器同步然后让硬件时钟和系统时间同步就是一个这样的例子。这将表现为时间上突然跳跃到 GC 并可能导致维持利用率目标的失败或者可能导致内存不足的错误。

在单个机器上运行多个 JVM 可能造成 JVM 之间相互干扰,使利用率图发生倾斜。alarm 线程是一个高优先级 RT 线程,它会抢占任何其他具有较低优先级的线程,而 GC 线程仍然以 RT 优先级运行。如果在任何时间都有足够的 GC 和 alarm 线程是活动的,则没有活动的 GC 循环的 JVM 可能使其应用程序线程被其他 JVM 的 GC 和 alarm 线程抢占,而时间实际上分配给了应用程序,原因是该 VM 的 GC 是不活动的。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130064/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130064/

相关推荐

模拟IC设计避坑:手把手教你用Cadence仿真恒定跨导轨到轨运放(附完整工艺库文件)

本文详细解析了使用Cadence仿真恒定跨导轨到轨运放的全流程,涵盖电路原理、工艺库配置、尺寸设计及仿真分析。通过1:3电流镜补偿技术实现跨导稳定,提供CSMC 0.25um工艺下的实战案例,帮助工程师规避设计陷阱,提升模拟IC设计效率。

weixin_30952535的博客 462

[转载]Java 理论与实践: 垃圾收集简史

Java 理论与实践: 垃圾收集简史Java 语言可能是使用最广泛的依赖于垃圾收集的编程语言,但是它并不是第一个。垃圾收集已经成为了包括 Lisp、Smalltalk、Eiffel、Haskell、ML、Scheme和 Mod...

congji3817的博客 126

R语言分段回归

1.利用虚拟变量进行线性回归 (MEdata.csv的数据来源:https://github.com/ChenQihome9/CSDN-Data-Library/blob/master/MEdata.csv) 问题13:针对TAX~GDP,使用分段回归方法,找到其回归方程。编程,并根据程序结果叙述如下几方面问题:(1)分几段最佳?(不超过5段内)(2)分段位置定在哪儿最佳?(3)写出最佳方...

ChenQihome9的博客 1万+

Java垃圾回收之老年代垃圾收集

1、Serial Old 收集器(-XX: +UseSerialOldGC, 标记-整理算法)   1.1 单线程收集,进行垃圾收集时,必须暂停所有工作线程   1.2 简单高效,Client模式下默认的老年代收集器 2、Parallel Od收集器(-XX: +UseParallelOldGC ,标记-整理)   2.1 JDK6 之后才有这个收集器,多线程,吞吐量优先,如果...

weixin_34409357的博客 988

第二季:7.怎么查看服务器默认的垃圾收集器是那个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?【Java面试题】

第二季:7.怎么查看服务器默认的垃圾收集器是那个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?【Java面试题】

小心星的博客 660

JDK5.0垃圾收集优化之--Don't Pause

      作者:江南白衣,最新版链接:http://blog.csdn.net/calvinxiu/archive/2007/05/18/1614473.aspx,版权所有,转载请保留原文链接。       原本想把题目更简单的定为--《不要停》的,但还是自己YY一下就算了。      Java开发Server最大的障碍,就是JDK1.4版之前的的串行垃圾收集机制会引起长时间的服务暂停,明白

花钱的年华 2万+

[转载]出色的“清洁工具” ― 理解 IBM Java 垃圾收集器,第一部分: 对象分配...

出色的“清洁工具” ― 理解 IBM Java 垃圾收集器,第一部分: 对象分配本文是关于 IBM Java 垃圾收集器(GC)系列文章(共三篇)的第一篇,IBM Java 垃圾收集器是用于 IBM Java 开发工具箱和运...

congji3817的博客 210

Java JVM JDK11 前的 7 个垃圾收集

专栏原创出处:github-源笔记文件 ,github-源码 ,欢迎 Star,转载请附上原文出处链接和本声明。 Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机 前言 JDK 11 前 HotSpot 虚拟机所包含的全部可用的垃圾收集器及组合关系如下图: 关于并发与并行的概念声明: 并行:说明同一时间有多条垃圾收集器...

2.wa 2130

第二季:8.G1垃圾收集器【Java面试题】

第二季:8.G1垃圾收集器【Java面试题】

小心星的博客 1056

CMS垃圾收集器与G1收集器及对应参数

转载:https://blog.csdn.net/rlnLo2pNEfx9c/article/details/79722384 1、CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下: 1)初始标记 2)并发标记 3)重新标记 4)并发清除 初始标记、从新标记这两个步骤仍然需要“stoptheworld”,初...

akunshouyoudou的博客 808

垃圾收集器(一)

1.1概述 垃圾收集器(Garbage Collection ,GC):Java语言中会自动对垃圾进行回收。但是GC需要完成它的功能是就必须思考: 那些内存需要回收? 什么时候回收? 如何回收 Java内存运行时区域的包括有,程序计数器、虚拟机栈、本地方法栈、Java堆、及方法区。其中:程序技术其、虚拟机栈、本地方法栈区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而...

weixin_34015860的博客 160

Java--垃圾回收【转载

一:垃圾回收机制的意义 java 语言中一个显著的特点就是引入了java回收机制,可以有效的防止内存泄露,有效的使用空闲的内存。 内存泄露:指该内存空间使用完毕后未回收,在不涉及复杂数据结构的一般情况下,java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。 内存溢出:应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用...

weixin_30394633的博客 122

【理解JVM】深入浅出JVM垃圾收集(HotSpot)

引言:我的上一篇博文提到JVM运行时的内存分块和GC的基本思想及算法,在GC的道路上越走越远,接下来要做的就是 深入浅出JVM的垃圾收集机制。一、分代垃圾回收1、新生代:大部分刚被创建的对象被分配到这里,生命周期短,被创建后很快变成不可达。在新生代区域内发生的GC称为minor GC。新生代被划分为三个区域: 1个Eden空间 2个Survivor空间 在节点拷贝法中,不会简单的将内存划分为1:1的

mxm691292118的博客 3076

JVM学习《垃圾回收算法和垃圾回收器》

因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(主要 是处理漏标问题),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。

GQ_666的博客 1627

Java高级进阶 2 内存分配策略和垃圾收集

对象存活判断算法 引用计数法 给对象添加引用计数器,每当一个地方引用该对象,计数器+1,引用失效,计时器-1。任何时刻对象引用计时器为0时,该对象不可被使用。引用计数法实现简单、效率高,但是主流虚拟机都为采用该算法进行内存管理,因为该算法无法解决对象直接互相循环引用的问题。 可达性分析算法 目前主流虚拟机采用的主流算法,通过可达性分析来判断对象是否存...

lbmydream的博客 282

java面试-G1垃圾收集

一、以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集器使用 eden + S0 + S1 进行复制算法 老年代收集必须扫描整个老年代区域 都是以尽可能的少而快速地执行 GC 为设计原则 二、G1 是什么 G1 是一种面向服务端的垃圾收集器,应用在多核处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集器的暂停时间要求。 像 C...

weixin_30532837的博客 369

G1 垃圾收集

一,垃圾回收   垃圾回收就是回收内存中不再使用的对象。   步骤有2步:     1. 查找内存中不再使用的对象       (1)引用计数法:有一个缺点,不能检测到环的存在       (2)根搜索算法:通过一系列名为“GC Roots”的对象作为起始点,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的     2.释放这些对象占用的内存    ...

dianzou1323的博客 141

《深入理解JAVA虚拟机》JDK的垃圾收集算法

概念 垃圾收集是很多使用JAVA语言的IT从业者了解得比较少的地方。 但是涉及性能时非常重要。大公司面试除了算法,这部分也是会经常考察的地方。 《深入理解JAVA虚拟机》一书中讲到JVM的垃圾收集算法和垃圾收集器。 垃圾收集算法分为: 1、标记清除算法 通常用在回收老年代内存。 最早的搜集算法就是标记清除(Mark-Sweep)算法了。 其原理是分为标记和清除两个阶...

dianfuwo9488的博客 171

JVM 垃圾回收就是这么简单(转载)

本篇转载自https://blog.csdn.net/qq_34337272/article/details/82177383 本节常见面试题: 问题答案在文中都有提到 如何判断对象是否死亡(两种方法)。 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 如何判断一个常量是废弃常量 如何判断一个类是无用的类 垃圾收集有哪些算法,各自的特点? HotSpot 为什么要分为新生代和老年代? 常见的垃圾回收器有那些? 介绍一下CMS,G1收集器。 Minor

heavenTear专栏 315

Java垃圾收集

Java垃圾收集转载自:@觅思.澈 http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html   概述   说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾

马凡贺的博客 434
上一篇: [转载]Eclipse插件Continuous Testing介绍
下一篇: [转载]用JSP/Servlets实现Web方式的文件上传
congji3817
博客等级 码龄10年 15粉丝 588原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值