关注性能: 谈论垃圾

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

相关文章

Zabbix在MySQL性能监控方面的运用及最佳实践记录

《Zabbix在MySQL性能监控方面的运用及最佳实践记录》Zabbix通过自定义脚本和内置模板监控MySQL核心指标(连接、查询、资源、复制),支持自动发现多实例及告警通知,结合可视化仪表盘,可有效... 目录一、核心监控指标及配置1. 关键监控指标示例2. 配置方法二、自动发现与多实例管理1. 实践步骤

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

MySQL 多列 IN 查询之语法、性能与实战技巧(最新整理)

《MySQL多列IN查询之语法、性能与实战技巧(最新整理)》本文详解MySQL多列IN查询,对比传统OR写法,强调其简洁高效,适合批量匹配复合键,通过联合索引、分批次优化提升性能,兼容多种数据库... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

JVisualVM之Java性能监控与调优利器详解

《JVisualVM之Java性能监控与调优利器详解》本文将详细介绍JVisualVM的使用方法,并结合实际案例展示如何利用它进行性能调优,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录1. JVisualVM简介2. JVisualVM的安装与启动2.1 启动JVisualVM2

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

PyTorch高级特性与性能优化方式

《PyTorch高级特性与性能优化方式》:本文主要介绍PyTorch高级特性与性能优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、自动化机制1.自动微分机制2.动态计算图二、性能优化1.内存管理2.GPU加速3.多GPU训练三、分布式训练1.分布式数据

Java的"伪泛型"变"真泛型"后对性能的影响

《Java的伪泛型变真泛型后对性能的影响》泛型擦除本质上就是擦除与泛型相关的一切信息,例如参数化类型、类型变量等,Javac还将在需要时进行类型检查及强制类型转换,甚至在必要时会合成桥方法,这篇文章主... 目录1、真假泛型2、性能影响泛型存在于Java源代码中,在编译为字节码文件之前都会进行泛型擦除(ty

JVM垃圾回收机制之GC解读

《JVM垃圾回收机制之GC解读》:本文主要介绍JVM垃圾回收机制之GC,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、死亡对象的判断算法1.1 引用计数算法1.2 可达性分析算法二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.4