学习IBM如何实现Java平台5.0版中内置的高级功能?
优采云 发布时间: 2021-08-10 04:20学习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 SDK5.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)和子池于子池) .
什么时候应该考虑非默认 GC 策略?
我建议您始终从默认的 GC 策略开始。在摆脱默认设置之前,您需要了解在什么情况下应该探索替代策略。表2列出了一系列可能的原因:
列出2.切换到备用GC策略的原因切换原因
optavgpause
gencon
子池
让我强调,表 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时间分配
Mutator 和 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 结束和并发阶段开始之间的差距是不同的,但在这个阶段,对性能没有显着影响。
Gencon
分代垃圾回收策略考虑对象的生命周期,将它们放在堆的单独区域。通过这种方式,它试图克服单个堆在大多数对象年轻和死亡的应用程序中的缺点,即这些对象不能承受多次垃圾采集。
使用代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启动或者发生压缩时,由于堆的可用区域很大,所以不使用子池。在这些情况下,每个处理器都有自己的专用迷你堆来满足请求。当第一次垃圾回收发生时,清除阶段开始填充子池,后续的分配主要使用子池。
sub-subpool 策略可以减少分配对象的时间。原子指令确保在不获取全局堆锁的情况下进行分配。处理器本地的小堆提高了效率,因为它减少了缓存干扰。这直接影响可扩展性,尤其是在多处理器系统上。在子池不可用的平台上,生成 GC 可以提供类似的好处。
结论
本文重点介绍 IBM SDK 5.0 中可用的不同 GC 策略选项及其一些特征。默认策略足以满足大多数应用程序的需求。但是,在某些情况下,其他策略效果更好。我已经介绍了一些一般情况,在这些情况下你应该考虑切换到 optavgpause、gencon 或 subpool。在评估策略时,衡量应用程序性能非常重要,第 2 部分将更详细地演示评估过程。
翻译自: