垃圾收集算法,垃圾收集器_垃圾收集政策

本文探讨了IBM SDK 5.0中四种不同的垃圾回收策略:优化吞吐量、优化暂停时间、代并发和子池化,分别适用于不同场景下的性能优化。文章深入分析了每种策略的特点及应用场景,帮助开发者根据具体需求选择最适合的垃圾回收策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可以使用四个不同的策略来配置用于Java 5.0平台的IBM开发人员套件(IBM SDK)中的垃圾收集(GC)。 本文是GC的两个部分的第一部分,介绍了不同的垃圾回收策略并讨论了它们的一般特征。 在开始之前,您应该对Java平台中的垃圾收集有基本的了解。 第2部分介绍了一种选择策略的定量方法,并提供了一些示例。

为什么要使用不同的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用于子池)。

什么时候应该考虑非默认GC策略?

我建议您始终从默认的GC策略开始。 在远离默认设置之前,您需要了解在什么情况下应该探索替代策略。 表2列出了可能适用的一系列原因:

表2.切换到备用GC策略的原因
切换到 原因
optavgpause
  • 我的应用程序无法忍受GC暂停的时间。 只要减少GC暂停时间,性能下降是可以接受的。
  • 我在64位平台上运行,并使用非常大的堆-超过3或4GB。
  • 我的应用程序是GUI应用程序,我担心用户的响应时间。
gencon
  • 我的应用程序分配了许多短期对象。
  • 堆空间是零散的。
  • 我的应用程序是基于事务的(也就是说,事务中的对象无法在事务提交之后继续存在)。
subpool
  • 我在大型多处理器计算机上遇到可伸缩性问题。

让我强调指出,表2中提到的原因不足以得出替代政策将表现更好的结论。 它们只是提示。 在所有情况下,您都应运行该应用程序,并结合GC暂停时间来测量吞吐量和/或响应时间。 本系列的下一部分显示了这种测试的示例。

本文的其余部分将详细介绍GC策略之间的区别。

光通量

optthruput是默认策略。 它是一个跟踪收集器,称为标记扫描紧凑收集器。 标记和扫描阶段始终在GC期间运行,但是压缩仅在某些情况下发生。 标记阶段查找并标记所有活动对象。 扫描阶段将删除所有未标记的对象。 第三和可选步骤是压实。 在各种情况下都可能发生压实。 最常见的一种是系统无法回收足够的可用空间。

当对象的分配和释放如此频繁,以至于堆中只剩下一小块可用空间时,就会发生碎片。 堆中总体上可能有大量可用空间,但是连续区域很小,从而导致分配失败。 压缩将所有对象向下移动到堆的开头并对齐它们,以使它们之间不存在空间。 这样可以消除堆中的碎片,但是这是一项昂贵的任务,因此仅在必要时执行。

图1显示了不同阶段后的堆布局的轮廓:标记,清除和压缩。 深色区域代表物体,明亮区域代表自由空间。

图1.垃圾回收前后的堆布局
垃圾回收前后的堆布局

不同的GC阶段如何工作的详细信息不在本文的讨论范围之内。 我的重点是确保您了解运行时特性。 我鼓励您阅读《诊断指南》(请参阅参考资料 )以获取更多信息。

图2说明了如何在应用程序线程(或mutator )和GC线程之间分配执行时间。 水平轴是经过的时间,垂直轴包含线程,其中n表示计算机上的处理器数量。 对于此说明,假定应用程序每个处理器使用一个线程。 GC由蓝色框表示,该框显示已停止的变异器和GC线程正在运行。 这些集合消耗了100%的CPU资源,并且转换器线程处于空闲状态。 该图过于笼统,因此我们可以将此策略与本文中的其他策略进行比较。 实际上,GC的持续时间和频率取决于应用程序和工作负载。

图2. optthruput策略中的mutator和GC线程之间的CPU时间分配
optthruput策略中的更改器和GC线程之间的CPU时间分配

堆锁和线程分配缓存

optthruput策略使用应用程序中所有线程共享的连续堆区域。 线程需要排他访问堆,以保留新对象的空间。 该锁称为堆锁,可确保一次仅一个线程可以分配一个对象。 在具有多个CPU的计算机上,此锁定可能会导致扩展问题,因为可以同时发生多个分配请求,但是每个分配请求都必须具有对堆锁的独占访问权限。

为减少此问题,每个线程保留一小部分内存,称为线程分配缓存 (也称为线程本地堆或TLH)。 这部分存储空间是线程专有的,因此从其分配内存时,不会使用堆锁。 当分配缓存已满时,线程将返回堆并使用堆锁请求一个新的堆。

堆的碎片可以防止线程获得较大的TLH,因此TLH会很快填满,从而导致应用程序线程频繁返回堆以换取新的TLH。 在这种情况下,堆锁成为瓶颈。 在这种情况下, gencon或子subpool策略可以提供很好的选择。

暂停

对于许多应用程序,吞吐量不如快速响应时间那么重要。 考虑一个处理工作项的时间不能超过100毫秒的应用程序。 随着GC暂停时间在100毫秒范围内,您将获得在此时间范围内无法处理的项目。 垃圾回收的问题在于暂停时间会增加处理项目所需的最长时间。 大堆大小(在64位平台上可用)增加了此效果,因为处理了更多对象。

optavgpause是另一种GC策略,旨在将暂停时间降至最低。 它不能保证特定的暂停时间,但暂停时间比默认GC策略产生的暂停时间短。 这个想法是在应用程序运行时同时执行一些垃圾收集工作。 这在两个地方完成:

  • 并发标记和清除 :在堆满之前,每个mutator都会帮助并标记sobject(并发标记)。 仍然有一个世界各地的GC,但暂停时间明显缩短了。 GC之后,增变器线程会帮助并清除对象(并发清除)。
  • 后台GC线程 :一个(或多个)低优先级后台GC线程在应用程序空闲时执行标记。

根据不同的应用程序,默认的GC策略会将吞吐量性能降低5%到10%。

图3说明了如何使用optavgpause在GC线程和mutator线程之间分配执行时间。 没有显示后台跟踪线程,因为它不会影响应用程序性能。

图3. optavgpause策略中的mutator和GC线程之间的CPU时间分配
optavgpause策略中的更改器和GC线程之间的CPU时间分配

图中的灰色区域表示已启用并发跟踪,并且每个mutator线程都必须放弃一些处理时间。 每个并发阶段之后都是完整的垃圾回收,完成了在并发阶段未发生的标记和清除。 由此产生的暂停应该比optthruput看到的正常GC短得多,如图3的时间刻度上的小框所示。GC的结束与并发阶段的开始之间的差距有所不同,但在在此阶段,不会对性能产生重大影响。

Gencon

分代垃圾回收策略会考虑对象的生存期,并将它们放置在堆的单独区域中。 通过这种方式,它尝试克服了大多数对象都年轻而死的应用程序中的单个堆的弊端,也就是说,这些对象无法承受许多垃圾回收。

使用世代GC,倾向于长期生存的对象与短期对象的区别对待。 如图4所示,将堆分为托儿所和保育 区 。在托儿所中创建对象,如果对象寿命足够长,则将其提升到保育区。 经过一定数量的垃圾回收后,对象将被提升。 这个想法是大多数对象都是短暂的。 通过频繁地收集托儿所,可以释放这些对象,而无需支付收集整个堆的成本。 租用区很少收集垃圾。

图4. gencon GC中的新旧区域
gencon垃圾收集中的新旧区

如您在图4中看到的,托儿所又分为两个空间: 分配和幸存者 。 将对象分配到分配空间中,当对象被填满时,将根据其年龄将活动对象复制到幸存者空间或保有权空间中。 然后,托儿所中的空间切换使用,其中分配成为生存者,而生存者成为分配者。 死对象占据的空间可以简单地被新分配覆盖。 苗圃的收集称为清道夫; 图5说明了此过程中发生的情况:

图5. GC之前和之后的堆布局示例
GC之前和之后的堆布局示例

当分配空间已满时,将触发垃圾回收。 然后跟踪活动对象并将其复制到幸存者空间。 如果大多数对象都已死亡,则此过程的成本确实不高。 此外,已达到复制阈值计数的对象将被提升到使用期限空间。 然后说该对象是永久性的。

正如它的名字代并发暗示的那样, gencon政策有其并发方面。 租用空间同时使用与optavgpause策略中使用的方法类似的方法进行标记,但不进行并发扫描。 在并发阶段,所有分配都支付少量的吞吐量税。 通过这种方法,从保有权空间收集中产生的暂停时间可以保持很小。

图6显示了运行gencon GC时执行时间的映射方式:

图6. gencon中的mutator和GC线程之间的CPU时间分布
gencon中的变体和GC线程之间的CPU时间分布

清道夫很短(红色小框所示)。 灰色表示开始并发跟踪,然后是占有权空间的集合,其中一些并发发生。 这称为全局集合 ,它既包括清除集合 ,也包括使用权空间集合。 全局收集发生的频率取决于堆大小和对象生存期。 租用空间的收集应该相对较快,因为大多数空间是同时收集的。

子池

subpool策略可以帮助提高多处理器系统上的性能。 如前所述,该策略仅在IBM pSeries和zSeries计算机上可用。 堆布局与optthruput策略相同,但是空闲列表的结构不同。 有多个列表,而不是整个堆有一个空闲列表,称为子池。 每个池都有一个相关的大小,可以根据这些大小对这些池进行排序。 通过转到具有该大小的池,可以快速满足一定大小的分配请求。 原子(与平台相关的)高性能指令用于从列表中弹出自由列表条目,从而避免了序列化访问。 图7显示了如何按大小组织存储的空闲块:

图7.按大小排序的子池空闲块
按大小排序的子池空闲块

当JVM启动或发生压缩时,由于堆的可用区域很大,因此不使用子池。 在这些情况下,每个处理器都有自己的专用迷你堆来满足请求。 当发生第一个垃圾回收时,清除阶段开始填充子池,随后的分配主要使用子池。

subpool策略可以减少分配对象所花费的时间。 原子指令可确保在不获取全局堆锁的情况下进行分配。 处理器本地的小型堆可提高效率,因为减少了缓存干扰。 这直接影响可伸缩性,尤其是在多处理器系统上。 在子subpool不可用的平台上,世代GC可以提供类似的好处。

结论

本文重点介绍了IBM SDK 5.0中可用的不同GC策略选择及其一些特征。 默认策略足以满足大多数应用程序的需要。 但是,在某些情况下,其他策略的效果更好。 我已经介绍了一些一般情况,在这些情况下,您应考虑切换到optavgpausegenconsubpool 。 在评估策略时衡量应用程序性能非常重要,第2部分将更详细地演示该评估过程。


翻译自: https://www.ibm.com/developerworks/java/library/j-ibmjava2/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值