关注性能: 谈论垃圾

2023-11-23 21:40
文章标签 性能 垃圾 关注 谈论

本文主要是介绍关注性能: 谈论垃圾,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关注性能: 谈论垃圾

您知道垃圾收集器在干什么吗?

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送



级别: 初级

Jack Shirazi (jack@JavaPerformanceTuning.com), 董事, JavaPerformanceTuning.com
Kirk Pepperdine (kirk@JavaPerformanceTuning.com), 首席技术官, JavaPerformanceTuning.com

2004 年 5 月 01 日

您的应用程序是否经常出现 out-of-memory 错误?用户是否感受到响应时间有些不稳定?应用程序是否在相当长的时间内变得没有响应?应用程序的性能是否显得迟缓了?如果对任何一个问题的回答是肯定的,那么您很可能遇到了垃圾收集的问题了。先别进行优化,且听听 JavaPerformanceTuning.com 的 Jack Shirazi 和 Kirk Pepperdine 来解释如何识别垃圾收集问题,并由此帮助您回答这个问题:您知道垃圾收集器在干什么吗?

许多开发人员认为,内存管理至多是开发业务逻辑的主要任务之外的一项不重要的工作 —— 直到业务逻辑不能像预期的或者测试时那样执行得好。出现这种情况时,就需要知道哪里出错了及其原因,这意味着要理解应用程序如何与底层计算资源(特别是内存)进行交互。理解应用程序如何利用内存的最好方式是观察垃圾收集器的行动。

为什么我的应用程序不连贯了?

Java 虚拟机中最大的一个性能问题是应用程序线程与同时运行的 GC 的互斥性。垃圾收集器要完成其工作,需要在一段时间内防止所有其他线程访问它正在处理的堆空间(内存)。按 GC 的术语,这段时间称为“stop-the-world”,并且,正如其名字所表明的,在垃圾收集器努力工作时,应用程序有一个急刹车。幸运的是,这种暂停通常是很短的,很难察觉到,但是很容易想像,如果应用程序在随机的时刻出现随机且较长时间的暂停,对应用程序的响应性和吞吐能力会有破坏性的影响。

不过 GC 只是应用程序出现不连贯和停顿的一个原因。那么如何确定 GC 对产生这些问题是否负有责任呢?要回答这个问题,我们需要测量垃圾收集器的工作强度,并当在系统中进行改变时继续这些测量,以定量地确定所做的改变是否有所期望的效果。

我需要多少内存?

普遍接受的信念是,在系统中添加内存将解决许多性能问题。虽然这个原则对于 JVM 来说经常是正确的,但是太多好东西可能对性能是有害的。因此技巧在于 Java 应用程序需要多少内存就给它多少,但是绝不多给。问题是,应用程序需要多少内存?对于应用程序不连贯的情况,我们需要观察垃圾收集行为以了解看它做的是否比所需要的更多。这些观察将告诉我们所做的改变是否有所期望的效果。





回页首


测量 GC 的活动

生成 GC 日志的标准方式是使用 -verbose:gc 旗标,设置这个旗标后,垃圾收集器会在每次运行时生成它所做的事情的汇总,一般是写入到控制台(通过标准输出或者标准错误)。许多 VM 支持一个允许 verbose GC 输出转向到一个文件的选项。例如,在 Sun 的 1.4 JVM 中,可以使用开关 -Xloggc:filename 将 GC 输出写到文件中。对于 HP JVM,要使用 -Xverbosegc=file 开关。在本文中,我们将分析 Sun 1.4.2 和 IBM 1.4.1 JVM 捕获的 verbose GC 输出。

使用这个方法监视内存使用的一个最大好处是它对应用程序的性能的影响很小。不幸的是,这个解决方案并不完美,因为这些日志文件可能变得特别大,而维护它们可能需要重新启动 JVM。尽管如此,这种技术在生产环境中仍然是可行的,因为它可以帮助诊断只在这种环境中才列出的性能问题。

更深入观察 GC

-verbose:gc 旗标生成的输出根据 JVM 厂商而不同,不同的垃圾收集器选项会报告特定于该实现的信息。例如,由 IBM JVM 生成的输出比由 Sun JVM 生成的输出冗长得多,而 Sun 的输出更适合于由工具读取。就是说,每一个 GC 日志传达基本信息 —— 使用了多少内存、恢复了多少内存、GC 周期用了多少时间,以及在收集期间是否采取了其他行动。从这些基本测量中,我们可以推断出有助于更好地理解所发生的事情的细节。我们要计算的统计如下所示:

  • 考虑的运行时的持续时间
  • 收集总数
  • 收集频率
  • 收集所用最长时间
  • 收集所用总时间
  • 收集所用平均时间
  • 收集的平均间隔
  • 分配的字节总数
  • 每次收集每秒分配的字节数
  • 恢复的字节总数
  • 每次收集每秒恢复的字节总数

理解了暂停时间,我们就可以理解 GC 对应用程序不响应是否负有部分或者全部责任了。一种实现这一任务的方法是将详细(verbose) GC 日志中的 GC 活动与系统采集的其他日志(如 Web 服务器日志中的请求 backlog)相对应。几乎可以肯定最长的 GC 暂停会导致整个系统响应可感觉的下降,所以知道什么时候响应下降是很有用的,这样就可以将 GC 活动与应用程序吞吐相关联起来。

另一种可能的竞争因素是堆内存分配和恢复的比率,称为 churn。产生大量立即释放的对象的应用程序通常会受到 churn 的拖累。更高的 churn 比率对垃圾收集器加以很大压力,创造了更多的内存资源竞争,这又可导致更长的暂停或者可怕的 OutOfMemoryError

了解应用程序是否遭遇这些问题的一个方法是测量所考虑的整个运行时期间 GC 所占用的总时间。有了这种计算,我们就可以了解 GC 做的是否比它所应该做的更多。让我们推导出进行这种判断所需要的公式。





回页首


Sun GC 日志记录

清单 1 是由 Sun 1.4.2_03 JVM 以 -Xloggc:filename 运行默认的标记-清除收集器所生成的日志记录的例子。可以看到,日志项非常精确地记录了每次所做的事情。



清单 1. 使用 -Xloggc:filename 旗标的 GC 日志记录
69.713: [GC 11536K->11044K(12016K), 0.0032621 secs]
69.717: [Full GC 11044K->5143K(12016K), 0.1429698 secs]
69.865: [GC 5958K->5338K(11628K), 0.0021492 secs]
69.872: [GC 6169K->5418K(11628K), 0.0021718 secs]
69.878: [GC 6248K->5588K(11628K), 0.0029761 secs]
69.886: [GC 6404K->5657K(11628K), 0.0017877 secs]

首先注意到的可能是每一项日志记录是写在一组方括号内的。其他 GC 算法,如并发收集器, 可能将一些值分解为更细的信息。如果是这种情况,这些被分解的值会由包围在嵌入的一组方括号中的细节所替代,这使工具可以更容易地处理详细 GC 输出。

我们的研究首先从分析清单 1 中标记为 69.713 的记录开始。这个标记是 JVM 开始后的秒数和毫秒数的时间戳。在这个例子中,JVM 在这个 GC 周期开始之前运行了 69.713 秒。从左到右的字段为:执行的收集的类型、GC 之前的堆使用、总的堆能力和 GC 事件的持续时间。从这个描述中我们可以看出第一个 GC 事件是一个小的收集。在 GC 开始之前,使用了 11536 Kb 的堆空间。在完成时,使用了 11044 Kb,堆能力为 12016 Kb,而整个收集用了 .0032621 秒。下一个事件,一个完全的 GC,在 69.717 秒时或者上一个小 GC 事件之后 0.003 秒时开始。注意,如果将小 GC 事件的持续时间加到其开始时间上,就会看到它在完全的 GC 开始之前不到 1毫秒结束。因此我们可以得出结论:小收集没有恢复足够的空间,这种失败触发了完全的 GC。对应用程序来说,这像是一个持续了 0.1462319 秒的事件。让我们继续确定如何计算其他值。

GC 日志记录的参数确定

我们通过确定每个 GC 日志记录中的值的参数来开始分析:

R(n) = T(n): [ <GC> HB->HE(HC), D]

n 清单中记录的索引,1 是第一个,m 是最后一个
R(n) GC 记录
T(n) 第 n 个 GC 发生的时间
HB GC 之前堆的数量
HE GC 之后使用的堆数量
HC 堆空间的总量
D GC 周期的持续时间

有了这些定义,我们现在可以推导出用于计算前面描述的值的公式。





回页首


基本值

我们要计算的第一个值是日志所覆盖的运行时整个持续时间。如果要考虑每一项记录,那么就要分析最后一项记录的时间戳。因为清单 1 只表示全部日志记录的一部分,我们需要从最后一项中提取出第一个时间戳。尽管对这个例子来说,这个数字足够精确,但是为了绝对准确,需要加上最后 GC 的持续时间。其原因是时间戳是在 GC 开始时记录的,而记录表示在记录了时间戳以后发生的事情。

剩余值是取记录中相应的值的总和计算的。值得注意的是恢复的字节可以通过分析记录中测量的关系而计算,而分配的字节可以通过分析前后记录测量之间的关系计算。例如,如果考虑在时间戳 69.872 和 69.878 之间发现的记录对,可以用第一个记录中 GC 之后占用的内存数量减去第二个记录在 GC 之前占用的字节数量计算在新的一代(generation)中分配的字节数量: 6248 Kb - 5418 Kb = 830 Kb 。下面表 1 展示了其他值的公式。

要找出最长的 GC 暂停,我们只需要查看持续时间并寻找 D(n) (记录 n 的持续时间)的最大值。

表 1. 统计公式

统计 计算(时间单位调整为秒)
运行时持续时间RT = (T(M) + D(M)) - T(1)
小收集的总数TMC = Sum( R(n)) 其中 GC(n) = GC
完全收集的总数TFC = Sum( R(n)) 其中 GC(n) = Full
收集频率(小收集)CFM = TMC / RT
收集频率(完全)CFF = TFC / RT
收集的时间(最长的小收集)MAX(D(n)) for all n 其中 GC(n) = GC
收集的时间(最长的完全收集)MAX(D(n)) for all n 其中 GC(n) = Full
小收集的时间(总数)TTMC = Sum(D(n)) for all n 其中 GC(n) = GC
完全收集的时间(总数)TTFC Sum(D(n)) for all n 其中 GC(n) = Full
收集的时间(总数)TTC = TTMC + TTFC
小收集的时间(平均)ATMC = TTMC / RT
完全收集的时间(平均)ATFC = TTFC / RT
收集的时间(平均)ATC = TTC / RT
收集间隔(平均)Sum( T(n+1) - T(n)) / (TMC + TFC) for all n
分配的字节(总数)TBA = Sum(HB(n+1) - HE(n)) 对于所有 n
分配的字节(每秒)TBA / RT
分配的字节(每次收集)TBA / (TMC + TFC)
小收集恢复的字节(总数)BRM = Sum(HB(n) - HE(n)) 其中 GC(n) = GC
完全收集恢复的字节(总数)BRF = Sum(HB(n) - HE(n)) 其中 GC(n) = Full
恢复的字节(总数)BRT = BRM + BRF
恢复的字节(每次小收集)BRPM = BRM / TMC
恢复的字节(每次完全收集)BRPF = BRF / TMF
恢复的字节(小收集每秒)BRP = BRM / TTMC
恢复的字节(完全收集每秒)BRF = BRF / TTFC

可以从公式中看出,我们经常需要分别考虑完全 GC 和小 GC。小 GC 与完全 GC 有根本性的不同,一般来说前者至少比后者要快一个数量级。我们可以通过快速分析清单 1 看出这一点 —— 最长的小收集比完全收集快 50 倍。

下面表 2 显示对清单 1 中的值使用表 1 中的公式的结果。

表 2. Sun GC 日志分析

统计 计算(时间单位调整为秒)
运行时持续时间(69.886 + 0.0017877) - 69.713 = 0.1747877
小收集总数5
完全收集总数1
收集频率(小收集)5 / 0.1747877 = 28.6 per second
收集频率(完全)1 / 0.1747877 = 5.27 per second
收集时间(最长的小收集)0.0032621
收集时间(最长的完全收集)0.1429698
小收集的时间(总数)0.0123469
完全收集的时间(总数)0.1429698
收集的时间(总数)0.1553167
小收集的时间(平均)7.1%
完全收集的时间(平均)81.8%
收集的时间(平均)88.9%
收集间隔(平均).173/5=0.0346
分配的字节(总数)3292
分配的字节(每秒)18834 Kb/second
分配的字节(每次收集)549 Kb
小收集恢复的字节(总数)3270 Kb
完全收集恢复的字节(总数)5901 Kb
恢复的字节(总数)5901 + 3270 = 9171 Kb
恢复的字节(每次小收集)3270/5 = 654
恢复的字节(每次完全收集)5901/1 = 5901
恢复的字节(小收集每秒)3270/0.0123469 = 264843 Kb/second
恢复的字节(完全收集每秒)5901/0.1429698 = 41274K/second

表 2 包含从这些看来简单的日志中推算出的大量信息。取决于所关注的问题,可能不需要计算所有这些值,因为其中一些值比另一些更有用。对于应用程序长时间不响应的情况,要关注的是 GC 持续时间和计数。





回页首


IBM GC 日志记录

与 Sun 日志不同,IBM 日志特别冗长。即使这样,仍然需要一个指导以完全理解所提供的信息。清单 2 是这种 verbose:gc 日志文件的一部分。



清单 2. IBM JVM verbose:gc 输出
<AF[31]: Allocation Failure. need 528 bytes, 969 ms since last AF>
<AF[31]: managing allocation failure, action=1 (0/97133320) (1082224/1824504)>
<GC(31): GC cycle started Wed Feb 25 23:08:41 2004
<GC(31): freed 36259000 bytes, 37% free (37341224/98957824), in 569 ms>
<GC(31): mark: 532 ms, sweep: 37 ms, compact: 0 ms>
<GC(31): refs: soft 0 (age >= 32), weak 0, final 2, phantom 0>
<AF[31]: managing allocation failure, action=3 (37341224/98957824)>
<GC(31): need to expand mark bits for 116324864-byte heap>
<GC(31): expanded mark bits by 270336 to 1818624 bytes>
<GC(31): need to expand alloc bits for 116324864-byte heap>
<GC(31): expanded alloc bits by 270336 to 1818624 bytes>
<GC(31): need to expand FR bits for 116324864-byte heap>
<GC(31): expanded FR bits by 544768 to 3637248 bytes>
<GC(31): expanded heap by 17367040 to 116324864 bytes, 47% free, ratio:0.417>
<AF[31]: completed in 812 ms>
<AF[32]: Allocation Failure. need 528 bytes, 1765 ms since last AF>
<AF[32]: managing allocation failure, action=1 (0/115394264) (930600/930600)>
<GC(32): GC cycle started Wed Feb 25 23:08:43 2004
<GC(32): freed 54489184 bytes, 47% free (55419784/116324864), in 326 ms>
<GC(32): mark: 292 ms, sweep: 34 ms, compact: 0 ms>
<GC(32): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
<AF[32]: completed in 328 ms>
<AF[33]: Allocation Failure. need 528 bytes, 1686 ms since last AF>
<AF[33]: managing allocation failure, action=1 (0/115510592) (814272/814272)>
<GC(33): GC cycle started Wed Feb 25 23:08:45 2004
<GC(33): freed 56382392 bytes, 49% free (57196664/116324864), in 323 ms>
<GC(33): mark: 285 ms, sweep: 38 ms, compact: 0 ms>
<GC(33): refs: soft 0 (age >= 32), weak 0, final 18, phantom 0>
<AF[33]: completed in 324 ms>

清单 2 中有三项 GC 日志记录。我将不会提供完全的说明,而是推荐一篇由 Sam Borman 所写的很好的文章“Sensible Sanitation”(请参阅 参考资料)。对于我们的目的,需要与像对 Sun JVM 的日志那样推算出同样类型的信息。好的方面是有一些计算结果已经是现成的了。例如,如果分析 AF[31] (事件 31 分配失败),将会看到 GC 之间的间隔、恢复的内存数量、事件的持续时间。我们可以根据这些数字计算其他所需要的值。





回页首


这些数字有什么意义

如何看待这些数字取决于所要得到的结果。在许多服务器应用程序中,它归结为缩短暂停时间,这又归结为减少所发生的完全收集的持续时间和次数。下个月,我们将探讨如何用这些信息调优曾经受这个问题困扰的一个真实应用程序。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 查看 关注性能系列的其他文章

  • Sam Borman 的“ Sensible Sanitation”讨论了 IBM JVM 垃圾收集器的细节( developerWorks,2002 年 8 月)。

  • Brian Goetz 在 “ Java theory and practice: Garbage collection and performance”( developerWorks,2004 年 1 月)中讨论了 GC。

  • 对于 Sun VM 的权威指导,请阅读 Tuning Garbage Collection with the 1.4.2 Java[tm] Virtual Machine

  • 在 Java 性能调优网站找到 GC 性能提示

  • 阅读 Jack Shirazi 的 Java Performance Tuning, 2nd Edition (O'Reilly)中关于 GC 的所有内容。

  • developerWorksJava 技术专区 中可以找到有关 Java 编程各个方面的数百篇文章。

  • 请访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。


作者简介

 

Jack Shirazi 是 JavaPerformanceTuning.com的董事,也是 Java Performance Tuning, 2nd Edition (O'Reilly) 一书的作者。


 

Kirk Pepperdine 是 Java Performance Tuning.com 的首席技术官,并且在过去 15 年一直关注对象技术和性能调优。Kirk 是 Ant Developer's Handbook (MacMillan)一书的合著者。

 

这篇关于关注性能: 谈论垃圾的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/420715

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

黑神话,XSKY 星飞全闪单卷性能突破310万

当下,云计算仍然是企业主要的基础架构,随着关键业务的逐步虚拟化和云化,对于块存储的性能要求也日益提高。企业对于低延迟、高稳定性的存储解决方案的需求日益迫切。为了满足这些日益增长的 IO 密集型应用场景,众多云服务提供商正在不断推陈出新,推出具有更低时延和更高 IOPS 性能的云硬盘产品。 8 月 22 日 2024 DTCC 大会上(第十五届中国数据库技术大会),XSKY星辰天合正式公布了基于星

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

HotSpot虚拟机的经典垃圾收集器

读《深入理解Java虚拟机》第三版笔记。 关系 Serial、ParNew、Parallel Scavenge、Parallel Old、Serial Old(MSC)、Concurrent Mark Sweep (CMS)、Garbage First(G1)收集器。 如图: 1、Serial 和 Serial Old 收集器 2、ParNew 收集器 3、Parallel Sc

PR曲线——一个更敏感的性能评估工具

在不均衡数据集的情况下,精确率-召回率(Precision-Recall, PR)曲线是一种非常有用的工具,因为它提供了比传统的ROC曲线更准确的性能评估。以下是PR曲线在不均衡数据情况下的一些作用: 关注少数类:在不均衡数据集中,少数类的样本数量远少于多数类。PR曲线通过关注少数类(通常是正类)的性能来弥补这一点,因为它直接评估模型在识别正类方面的能力。 精确率与召回率的平衡:精确率(Pr

浅谈PHP5中垃圾回收算法(Garbage Collection)的演化

前言 PHP是一门托管型语言,在PHP编程中程序员不需要手工处理内存资源的分配与释放(使用C编写PHP或Zend扩展除外),这就意味着PHP本身实现了垃圾回收机制(Garbage Collection)。现在如果去PHP官方网站(php.net)可以看到,目前PHP5的两个分支版本PHP5.2和PHP5.3是分别更新的,这是因为许多项目仍然使用5.2版本的PHP,而5.3版本对5.2并不是完

SQL2005 性能监视器计数器错误解决方法

【系统环境】 windows 2003 +sql2005 【问题状况】 用户在不正当删除SQL2005后会造成SQL2005 性能监视器计数器错误,如下图 【解决办法】 1、在 “开始” --> “运行”中输入 regedit,开启注册表编辑器,定位到 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVer