算法 自动采集列表(学习IBM如何实现Java平台5.0版中内置的高级功能?)
优采云 发布时间: 2021-11-06 16:15算法 自动采集列表(学习IBM如何实现Java平台5.0版中内置的高级功能?)
可以使用四种不同的策略在 IBM Developer Suite (IBM SDK) 中为 Java 5.0 平台配置垃圾回收 (GC)。本文是GC两部分的第一部分。它介绍了不同的垃圾采集策略并讨论了它们的一般特征。在开始之前,您应该对 Java 平台中的垃圾采集有一个基本的了解。第 2 部分介绍了选择策略的定量方法并提供了一些示例。
关于系列
Java 技术,IBM 风格系列介绍了 Java 平台的 IBM 实现的最新版本。您将了解 IBM 如何实现 Java 平台5.0 版本中内置的一些高级特性,并了解如何使用新 IBM 版本中内置的一些增值特性。
如有任何意见或问题,请联系作者。要对整个系列发表评论,请联系系列负责人 Chris Bailey。有关此处讨论的概念的更多信息以及可从中下载最新 IBM 版本的链接,请参阅。
为什么要使用不同的 GC 策略?
不同 GC 策略的可用性为您提供了增强的功能。GC 有许多不同的算法可用,根据工作负载的类型,每种算法都有自己的优点和缺点。(如果不熟悉GC算法的一般话题,请参考进一步阅读。)在IBM SDK 5.0中,您可以使用四种策略之一来配置垃圾采集器,每种策略使用自己的算法。对于大多数应用程序,默认策略就足够了。如果您对应用程序没有任何特定的性能要求,那么本文(以及下一篇)的内容可能对您不感兴趣。您可以在不更改 GC 策略的情况下运行 IBM SDK 5.0。但是,如果您的应用程序需要最佳性能,或者你普遍担心GC暂停时间,请继续阅读。您将看到最新版本的 IBM 比以前的版本有更多的选项。
那么,为什么没有自动为您选择 Java 运行时的 IBM 实现?这并不总是可能的。在运行时很难理解您的需求。在某些情况下,您可能希望以高吞吐量运行应用程序。在其他情况下,您可能希望减少暂停时间。
表 1 列出了可用的策略并指出了何时应使用每种策略。以下部分将详细介绍每种策略的特点。
表 1. IBM SDK 5.0 中的 GC 策略策略选项说明
优化吞吐量
-Xgcpolicy:optthruput(可选)
默认策略。它通常用于原创吞吐量比短暂的 GC 暂停更重要的应用程序。每次采集垃圾时,应用程序都会停止。
优化暂停时间
-Xgcpolicy:optavgpause
通过同时执行一些垃圾采集,高吞吐量被交换为更短的 GC 暂停。申请被暂停了很短的时间。
生成并发
-Xgcpolicy:gencon
短期对象的处理方式与长期对象不同。通过这种策略,具有许多短期对象的应用程序可以看到更短的暂停时间,同时仍然产生良好的吞吐量。
子池化
-Xgcpolicy:subpool
使用类似于默认策略的算法,但使用更适合多处理器计算机的分配策略。对于具有 16 个或更多处理器的 SMP 计算机,我们建议使用此策略。此策略仅适用于 IBMpSeries® 和 zSeries® 平台。需要在大型计算机上扩展的应用程序可以从这种策略中受益。
定义了一些术语
吞吐量是应用程序处理的数据量。必须使用特定于应用程序的指标来衡量吞吐量。
暂停时间是垃圾采集器暂停所有应用程序线程以采集堆的持续时间。
在本文中,我使用表 1 中详述的命令行选项的缩写来表示以下策略:用于优化吞吐量的 optthruput)、用于优化暂停时间的 optavgpause)、用于分代并发的 gencon)和用于 subpool 的 subpool)。
什么时候应该考虑非默认 GC 策略?
我建议您始终从默认的 GC 策略开始。在摆脱默认设置之前,您需要了解在什么情况下应该探索替代策略。表2列出了一系列可能的原因:
表2.切换到备用GC策略的原因切换到原因
暂停
基因组
子池
让我强调,表 2 中提到的原因不足以得出替代政策会表现更好的结论。它们只是提示。在所有情况下,您都应该运行应用程序并结合 GC 暂停时间来测量吞吐量和/或响应时间。本系列的下一部分将展示此类测试的示例。
本文的其余部分将详细介绍 GC 策略之间的差异。
光通量
一堆碎片的迹象
堆碎片最常见的迹象是过早的分配失败。这被认为是在详细垃圾采集 (-Xverbose:gc) 中触发的 GC,即使堆上有可用空间。另一个标志是存在小线程分配缓冲区(请参阅“”部分)。您可以使用 -Xtgc:freelist 来确定平均大小。IBM SDK 5 诊断指南中解释了这两个选项。
optthruput 是默认策略。它是一个跟踪采集器,称为标记扫描紧凑采集器。标记和扫描阶段总是在 GC 期间运行,但压缩仅在某些情况下发生。标记阶段查找并标记所有活动对象。所有未标记的对象将在扫描阶段被删除。第三步也是可选的步骤是压实。压实可以在各种情况下发生。最常见的一种是系统无法回收足够的可用空间。
当对象被频繁地分配和释放以致堆中只剩下一小块空闲空间时,就会发生碎片。堆整体可能有很多空闲空间,但是连续区域很小,导致分配失败。压缩将所有对象向下移动到堆的开头并对齐它们,以便它们之间没有空间。这消除了堆中的碎片,但这是一项昂贵的任务,因此仅在必要时执行。
图 1 显示了经过不同阶段的堆布局的轮廓:标记、清除和压缩。暗区代表物体,亮区代表自由空间。
扫描它
标记阶段遍历所有可以从线程堆栈、静态变量、插入的字符串和 JNI 引用中引用的对象。作为此过程的一部分,我们将创建一个标记位向量来定义所有活动对象的开头。
扫描阶段使用标记阶段生成的标记位向量来识别堆中存储的块,可以回收这些块以备将来分配;这些块被添加到空闲列表中。
图1. 垃圾回收前后的堆布局
不同 GC 阶段如何工作的细节超出了本文的范围。我的观点是确保您了解运行时特征。我鼓励您阅读“诊断指南”(请参阅)以获取更多信息。
图 2 说明了如何在应用程序线程(或 mutator)和 GC 线程之间分配执行时间。横轴是经过时间,纵轴是线程,其中n代表计算机的处理器数量。在此描述中,假设应用程序使用每个处理器一个线程。GC 用蓝色框表示,表示停止的突变体和 GC 线程正在运行。这些集合消耗了 100% 的 CPU 资源,并且转换器线程处于空闲状态。该图过于笼统,因此我们可以将此策略与本文中的其他策略进行比较。实际上,GC 的持续时间和频率取决于应用程序和工作量。
图2. optthruput策略中mutator和GC线程之间的CPU时间分配
Mutators 和 GC 线程
mutator 线程是分配对象的应用程序。mutator 的另一个词是应用程序。GC 线程是内存管理的一部分并执行垃圾采集。
堆锁和线程分配缓存
optthruput 策略使用由应用程序中的所有线程共享的连续堆区域。线程需要独占访问堆来为新对象保留空间。这种锁称为堆锁,可确保一次只有一个线程可以分配一个对象。在具有多个 CPU 的计算机上,此锁可能会导致扩展问题,因为可以同时发生多个分配请求,但每个分配请求都必须对堆锁具有独占访问权限。
为了减少这个问题,每个线程都会保留一小部分内存,称为线程分配缓存(也称为线程本地堆或TLH)。这部分存储空间是线程特定的,因此当从中分配内存时,不会使用堆锁。当分配缓冲区已满时,线程将返回堆并使用堆锁请求新的堆。
堆的碎片化会阻止线程获得更大的TLH,因此TLH会很快填满,导致应用线程频繁返回堆以换取新的TLH。在这种情况下,堆锁成为瓶颈。在这种情况下,gencon 或 sub-subpool 策略可以提供一个不错的选择。
暂停
对于许多应用程序,吞吐量不如快速响应时间重要。考虑一个工作项的处理时间不能超过 100 毫秒的应用程序。由于 GC 暂停时间在 100 毫秒内,您将获得在此时间范围内无法处理的项目。垃圾回收的问题在于暂停时间会增加处理项目所需的最长时间。大堆大小(在 64 位平台上可用)增加了这种效果,因为处理了更多的对象。
optavgpause 是另一种旨在最小化暂停时间的 GC 策略。它不能保证特定的暂停时间,但暂停时间比默认 GC 策略生成的暂停时间短。这个想法是在应用程序运行时执行一些垃圾采集。这是在两个地方完成的:
根据不同的应用,默认的 GC 策略会降低 5% 到 10% 的吞吐量性能。
图 3 说明了如何使用 optavgpause 在 GC 线程和 mutator 线程之间分配执行时间。后台跟踪线程未显示,因为它不会影响应用程序性能。
图3. optavgpause策略中mutator和GC线程的CPU时间分配
图中灰色区域表示已启用并发跟踪,每个mutator线程必须放弃一些处理时间。每个并发阶段完成一次垃圾回收后,完成并发阶段未发生的标记和清除。由此产生的暂停应该比 optthruput 看到的正常 GC 短很多,如图 3 时间刻度上的小框所示。 GC 结束和并发阶段开始之间的差距是不同的,但在这个阶段,对性能没有显着影响。
创康
分代垃圾回收策略考虑对象的生命周期,将它们放在堆的单独区域。通过这种方式,它试图克服单个堆在大多数对象年轻和死亡的应用程序中的缺点,即这些对象不能承受多次垃圾采集。
使用分代 GC 倾向于将长期对象与短期对象区别对待。如图 4 所示,该桩分为育苗区和育苗区。在托儿所中创建对象,如果对象存活时间足够长,则将它们提升到托儿所区域。经过一定量的垃圾采集后,该对象将被提升。这个想法是大多数对象都是短暂的。通过频繁采集托儿所,可以释放这些对象,而无需支付采集整个堆的成本。租用区域很少采集垃圾。
图4. gencon GC中的新旧区
如图 4 所示,托儿所分为两个空间:分配和幸存者。将对象分配到分配的空间。当对象已满时,活动对象将根据其年龄被复制到survivor空间或tenure空间。然后,将托儿所中的空间切换为使用,分配成为幸存者,幸存者成为分配者。死对象占用的空间可以简单地被新分配覆盖。苗圃采集被称为清道夫;图 5 说明了此过程中发生的情况:
图5. GC前后堆布局示例
当分配的空间已满时,将触发垃圾回收。然后跟踪活动对象并将其复制到幸存者空间。如果大部分对象都死了,这个过程的成本真的不高。此外,达到复制阈值计数的对象将被提升到到期空间。然后说对象是永久的。
顾名思义,它代表并发性,gencon 策略有它的并发性方面。租用的空间也使用类似于 optavgpause 策略中使用的方法进行标记,但不执行并发扫描。在并发阶段,所有分配都会支付少量的吞吐量税。通过这种方式,可以将tenure space的采集产生的暂停时间保持在很小的范围内。
图 6 显示了在运行 gencon GC 时如何映射执行时间:
图6. gencon中mutator和GC线程的CPU时间分布
清道夫很短(显示在小红框中)。灰色表示开始并发跟踪,然后是占用空间的集合,其中一些是并发发生的。这称为全局集,它包括清算集和使用权空间集。全局采集发生的频率取决于堆大小和对象生命周期。租用空间的采集应该是比较快的,因为大部分空间是同时采集的。
子池
子池策略有助于提高多处理器系统的性能。如前所述,此策略仅适用于 IBM pSeries 和 zSeries 计算机。堆布局与 optthruput 策略相同,但空闲列表的结构不同。有多个列表而不是整个堆的空闲列表,称为子池。每个池都有一个关联的大小,可以根据这些大小对池进行排序。通过去到这个大小的池,可以快速满足一定大小的分配请求。原子(平台相关)高性能指令用于从列表中弹出空闲列表条目,从而避免序列化访问。图 7 显示了如何按大小组织存储的空闲块:
图7.按大小排序的子池的空闲块
当JVM启动或者发生压缩时,由于堆的可用区域很大,所以不使用子池。在这些情况下,每个处理器都有自己的专用迷你堆来满足请求。当第一次垃圾回收发生时,清除阶段开始填充子池,后续的分配主要使用子池。
子池策略可以减少分配对象所需的时间。原子指令确保在不获取全局堆锁的情况下进行分配。处理器本地的小堆提高了效率,因为它减少了缓存干扰。这直接影响可扩展性,尤其是在多处理器系统上。在子池不可用的平台上,生成 GC 可以提供类似的好处。
综上所述
本文重点介绍 IBM SDK 5.0 中可用的不同 GC 策略选项及其一些特性。默认策略足以满足大多数应用程序的需求。但是,在某些情况下,其他策略效果更好。我已经介绍了一些一般情况,在这些情况下你应该考虑切换到 optavgpause、gencon 或 subpool。在评估策略时,衡量应用程序性能非常重要,第 2 部分将更详细地演示评估过程。
翻译自: