Java性能权威指南-总结8

news/2025/4/1 5:09:39/

Java性能权威指南-总结8

  • 垃圾收集算法
    • 理解CMS收集器
      • 针对并发模式失效的调优

垃圾收集算法

理解CMS收集器

针对并发模式失效的调优

调优CMS收集器时最要紧的工作就是要避免发生并发模式失效以及晋升失败。 正如在CMS垃圾收集日志中看到的那样,发生并发模式失效往往是由于CMS不能以足够快的速度清理老年代空间:新生代需要进行垃圾回收时,CMS收集器计算发现老年代没有足够的空闲空间可以容纳这些晋升对象,不得不先对老年代进行垃圾回收。

初始时老年代空间中对象是一个接一个整齐有序排列的。当老年代空间的占用达到某个程度(默认值为70%)时,并发回收就开始了。一个CMS后台线程开始扫描老年代空间,寻找无用的垃圾对象时,竞争就开始了:CMS收集器必须在老年代剩余的空间(30%)用尽之前,完成老年代空间的扫描及回收工作。 如果并发回收在这场速度的比赛中失利,CMS收集器就会发生并发模式失效。

有以下途径可以避免发生这种失效:

  • 想办法增大老年代空间,要么只移动部分的新生代对象到老年代,要么增加更多的堆空间。
  • 以更高的频率运行后台回收线程。
  • 使用更多的后台回收线程。
自适应调优和CMS垃圾搜集
CMS收集器使用两个配置MaxGCPauseMllis=N和GCTimeRatio=N来确定使用多大的堆和多大的代空间。CMS收集与其他的垃圾收集方法一个显著的不同是除非发生Full GC,否则CMS的新生代大小不会作调整。由于CMS的目标是尽量避免Full GC,
这意味着使用精细调优的CMS收集器的应用程序永远不会调整它的新生代大小。程序启动时可能频发并发模式失效,因为CMS收集器需要调整堆和永久代(或者元空间)的大小。使用CMS收集器,初始时采用一个比较大
的堆(以及更大的永久代/元空间)是一个很好的主意,这是一个特例,增大堆的大小反而帮助避免了那些失效。

如果有更多的内存可用,更好的方案是增加堆的大小,否则可以尝试调整后台线程运行的方式来解决这个问题。

  1. 给后台线程更多的运行机会

为了让CMS收集器赢得这场比赛,方法之一是更早地启动并发收集周期。显然地,CMS收集器在老年代空间占用达到60%时启动并发周期,这和老年代空间占用到70%时才启动相比,前者完成垃圾收集的几率更大。为了实现这种配置,最简单的方法是同时设置下面这两个标志:-XX:CMSInitiatingOccupancyFraction=N-XX:+UseCMSInitiatingoccupancyonly。同时使用这两个参数能帮助CMS更容易地进行决策:如果同时设置这两个标志,那么CMS就只依据设置的老年代空间占用率来决定何时启动后台线程。 默认情况下,UseCMSInitiatingoccupancyOnly标志的值为假,CMS会使用更复杂的算法判断什么时候启动并行收集线程。如果有必要提前启动后台线程,推荐使用最简单的方法,即将UseCMSInitiatingOccupancyonly标志的值设置为真。

CMSInitiatingOccupancyFraction参数值的调整可能需要多次迭代才能确定。如果开启了UseCMSInitiatingoccupancyonly标志,CMSInitiatingoccupancyFraction的默认值就被置为70,即CMS会在老年代空间占用达到70%时启动并发收集周期。

对特定的应用程序,该标志的更优值可以根据GC日志中CMS周期首次启动失败时的值得到。具体方法是,在垃圾回收日志中寻找并发模式失效,找到后再反向查找CMS周期最近的启动记录。日志中含有CMS-initial-mark信息的一行包含了CMS周期启动时,老年代空间的占用情况如下所示:

89.976:[GC [1 CMS-initial-mark: 702254K(1398144K)]772530K(2027264K),0.0830120 secs][Times:user=0.08 sys=0.00,real=0.08 secs]

在这个例子中,根据日志的输出,我们可以判断该时刻老年代空间的占用率为50%(老年代空间大小为1398 MB,其中702MB被占用)。不过这个值还不够早,因此我们需要调整CMSInitiatingOccupancyFraction将其值设定为小于50的某个值。(虽然CMSInitiatingOccupancyFraction的默认值为70,不过这个例子中没有开启UseCMSInitiatingoccupancyonly标志,所以例子中CMS收集器在老年代空间占用达到50%时启动了CMS后台线程。)

了解了CMSInitiatingOccupancyFraction的工作原理之后,可能会有疑问,能不能将参数值设置为0或者其他比较小的值,让CMS的后台线程持续运行。通常不推荐进行这样的设置,但是,如果对其中的取舍非常了解,适当地妥协也是可以接受的。

这其中的第一个取舍源于CPU:CMS后台线程会持续运行,它们会消耗大量的CPU时钟——每个CMS后台线程运行时都会100%地占用一颗CPU。多个CMS线程同时运行时还会有短暂的爆发,机器的总CPU使用因此也会暴涨。如果这些线程都是毫无目的地持续运行,只会白白浪费宝贵的CPU资源。

另一方面,这并不是说使用了过多的CPU周期就是问题。后台的CMS线程需要时必须运行,即使在最好的情况下,这也是很难避免的。因此,机器必须预留足够的CPU周期来运行这些CMS线程。所以规划机器时,你必须考虑留出余量给这部分CPU的使用。

CMS周期中,如果CMS后台线程没有运行,这些CPU时钟可以用于运行其他的应用吗?通常不会。如果还有另一个应用也在使用同一个时钟周期,它没有途径了解何时CMS线程会运行。因此,应用程序线程和CMS线程会竞争CPU资源,而这很可能会导致CMS线程的“失速”(lose its race)。有些时候,通过复杂的操作系统调优,有可能让应用线程以低于CMS线程优先级的方式让两种线程在同一个时钟周期内运行,但是这些方法都相当复杂,很容易出错。因此,答案是肯定的,CMS周期运行得越频繁,CPU周期越长,如果不这样,这些CPU周期就是空闲状态(idle)。

第二个取舍更加重要,它与应用程序的停顿相关。正如在GC日志中观察到的,CMS在特定的阶段会暂停所有的应用线程。 使用CMS收集器的主要目的就是要限制GC停顿的影响,因此频繁地运行更多无效的CMS周期只能适得其反。CMS停顿的时间与新生代的停顿时间比起来要短得多,应用线程甚至可能感受不到这些额外的停顿——这也是一种取舍,是要避免额外的停顿还是要减少发生并发模式失败的几率。不过,正如前面提到的,持续地运行后台GC线程所造成的停顿可能会导致总体的停顿,而这最终会降低应用程序的性能。

除非这些取舍都能接受,否则不要将CMSInitiatingoccupancyFraction参数的值设置得比堆内的活跃数据数还多,至少要少10%到20%。

  1. 调整CMS后台线程

每个CMS后台线程都会100%地占用机器上的一颗CPU。如果应用程序发生并发模式失效,同时又有额外的CPU周期可用,可以设置-XX:ConcGCThreads=N标志,增加后台线程的数目。默认情况下,ConcGCThreads的值是依据ParallelGCThreads标志的值计算得到的:

	ConcGCThreads = (3 + ParallelGCThreads) / 4

上述计算使用整数计算方法,这意味着如果ParallelGCThreads的取值区间在1到4,ConcGCThread的值就为1,如果ParallelGCThreads的取值在5到8之间,ConcGCThreads的值就为2,以此类推。

调整这一标志的要点在于判断是否有可用的CPU周期。如果ConcGCThreads标志值设置的偏大,垃圾收集会占用本来能用于运行应用线程的CPU周期;最终效果上,这种配置会导致应用程序些微的停顿,因为应用程序线程需要等待再次在CPU上继续运行的机会。

除此之外,在一个配备了大量CPU的系统上,ConcGCThreads参数的默认值可能偏大。如果没有频繁遭遇并发模式失败,可以考虑减少后台线程数,释放这部分CPU周期用于应用线程的运行。

快速小结

  1. 避免发生并发模式失效是提升CMS收集器处理能力、获得高性能的关键。
  2. 避免并发模式失效(如果有可能的话)最简单的方法是增大堆的容量。
  3. 否则,我们能进行的下一个步骤就是通过调整CMSInitiatingOccupancy-Fraction参数,尽早启动并发后台线程的运行。
  4. 另外,调整后台线程的数目对解决这个问题也有帮助。

http://www.ppmy.cn/news/271732.html

相关文章

C++ vector容器

1. 定义初始化vector对象 vector<string> vStr; // 空容器vStr.push_back("ABC"); vStr.push_back("123");vector<string> vStr1(vStr); // 拷贝构造 vector<string> vStr2 vStr; // 拷贝构造// C 11 vector<string> vStr3 { …

ipad2升级ios6详细图文教程

http://www.pc6.com/edu/58658.html

华为手机如何升级鸿蒙系统_华为鸿蒙2.0系统怎么进行升级?鸿蒙2.0系统升级教程...

华为鸿蒙2.0系统怎么进行升级&#xff1f;很多用户都还不太清楚这个鸿蒙2.0系统升级的方法&#xff0c;那么今天就让浏览器小编为大家带来&#xff0c;鸿蒙2.0系统升级教程。 华为鸿蒙2.0系统怎么进行升级&#xff1f; 华为EMUI11新系统在9月举行的华为开发者大会上正式发布&am…

magic2怎样升级HarmonyOS,鸿蒙2.0怎么升级 华为鸿蒙新系统升级方法步骤

最近华为新出了鸿蒙系统&#xff0c;很多网友都想更新尝试一下&#xff0c;现在鸿蒙系统已经开启了公测&#xff0c;大家都非常的想更新试一试体验一下&#xff0c;但是很多的网友都不知道该如何升级&#xff0c;其实一些支持首批公测的机型&#xff0c;是需要公测申请后才可以…

android平板 可以刷ios,终于跟上安卓!iPad Pro新功能曝光:系统升级方便了

iOS和iPadOS被视为iPhone和iPad的最大优势&#xff0c;然而苹果系统总会有一些奇奇怪怪的设定&#xff0c;就比如系统升级方面&#xff0c;iPhone 12系列之前&#xff0c;所有的iPhone都只能使用Wi-Fi更新系统。直到iPhone 12系列&#xff0c;才支持使用5G蜂窝网络更新系统。 5…

ipad mini2 12.5.4成功降级 10.3.3

手里的ipad mini2 是16G WIFI版本。15年屏幕进水后内屏排线被腐蚀后屏幕无法点亮。 2020年疫情影响下&#xff0c;沉寂近5年后&#xff0c;通过更换配件&#xff0c;成功救活。不过系统版本太旧&#xff08;估计是ios9&#xff09;,部分常用软件已不支持&#xff0c;被迫通过系…

第四十八回:TabBar Widget

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了MaterialApp Widget相关的内容,本章回中将介绍 TabBar Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在这里说的TabBar Widget是指屏幕顶部的标签&#xff0c;通常情况下在一个界面的顶部有…

【数据结构与算法】线性表 01 链表

一、线性表1.1 概念与特点1.2 线性表的存储结构1.3 常见操作1.4 应用场景 二、链表2.1 链表简介2.2 单向链表&#xff08;单链表&#xff09;2.21 基本概念2.22 单链表基本操作2.23 C语言实现 2.3 双向链表2.4 循环链表 一、线性表 线性表是一种最基本、最简单的数据结构&…