有几个原因可能导致您需要仔细研究应用程序中的垃圾回收(GC)。 您可能会担心应用程序的内存使用模式:是否使用了过多的内存? 内存泄漏了吗? 内存使用是否可以长期持续? 您可能还对使应用程序更快地执行感兴趣。 垃圾收集会对应用程序性能产生很大影响。 大多数人都知道配置不当的GC会占用大量资源,并降低应用程序运行速度。 但是,情况也相反:明智地选择垃圾回收参数实际上可以使应用程序运行更快。
在短暂的Java应用程序中或在性能不是很重要的应用程序中,GC可以被轻松忽略。 在其他情况下,使用工具可以使从详细的GC日志中获取所需信息变得更加容易。 该工具可以可视化堆中正在发生的事情,从而更容易发现模式,甚至可以为您指出一些模式并提出调整建议。
GC和Memory Visualizer是IBM新工具套件的一部分,该套件分析详细的GC日志,以帮助提供对内存管理问题的这种见解。 在本文中,您将了解GC和Memory Visualizer的功能,并查看一些示例场景,其中GC和Memory Visualizer可帮助您诊断内存问题。
GC和Memory Visualizer可以处理来自版本1.4.2或更高版本的所有IBM JRE的日志。 它还可以可视化来自IBMWebSphere®Real Time的日志。 使用它,您可以同时比较多个日志,放大日志的特定区域,过滤数据并以一定单位显示。 图1显示了GC和Memory Visualizer显示的示例:
图1.示例GC和Memory Visualizer显示
启用详细的GC日志记录
如果要生成用于分析的日志,则必须为应用程序启用详细的GC日志记录。 对于版本5.0或更高版本的IBM VM,可以使用-verbose:GC
虚拟机(VM)标志或-XverboseGClog: file
命令来完成。 在这里, file
是您选择的日志文件的名称。 如果可用,则首选-XverboseGClog
选项。 详细GC通常对应用程序的性能影响相对较小。
下载并安装GC和Memory Visualizer
GC和Memory Visualizer可在IBM Support Assistant中免费下载。 如果尚未安装IBM Support Assistant,则需要先下载它。 (请参阅相关信息中的链接。)一旦安装了IBM Support Assistant,你需要让与IBM Support Assistant知道您使用的产品,其中包括一个JVM。 这是通过安装产品插件来完成的, 如图2所示。 产品插件在IBM Support Assistant的Updater页面上下载。 例如,您可能想从“其他”部分中选择一种Java开发工具包,或者从“ WebSphere”部分中选择WebSphere产品之一。
图2.安装产品插件
同时,您可以安装GC和Memory Visualizer插件。 GC和Memory Visualizer位于“通用插件工具”部分的“新插件”选项卡下,如图3所示:
图3.安装GC和Memory Visualizer
在安装产品插件以及GC和Memory Visualizer之后,您需要重新启动IBM Support Assistant。 GC和Memory Visualizer将可以在“工具”页面上启动,如图4所示:
图4.启动GC和Memory Visualizer
常见任务
现在,让我们使用GC和Memory Visualizer执行一些基本的日志分析任务。
打开日志进行分析
要使用GC和Memory Visualizer分析详细的GC日志,请启动GC和Memory Visualizer,然后从“文件”菜单中选择“ 打开文件 ”。 GC和Memory Visualizer会在带有四个选项卡的编辑器中打开日志,如图5所示。尽管GC和Memory Visualizer不会自动更新显示,但您可以从正在运行的应用程序中打开日志。 要刷新显示,请单击“ 重置轴”按钮。
图5. GC和Memory Visualizer编辑器中的选项卡
选项卡如下:
- 标有文件名的选项卡显示日志本身的文本。 如果日志很大,则GC和Memory Visualizer不会显示所有文本,但是整个日志仍会被解析。
- “数据”选项卡显示了由GC和Memory Visualizer生成的数据的原始视图。 此数据适合剪切和粘贴到电子表格中。
- 线图选项卡显示数据的可视化。
- “报告”选项卡显示GC和Memory Visualizer的数据报告,其中包含每个选定字段的摘要,整个日志的表格摘要以及一系列调整建议。
VGC Data菜单(如图6所示)显示了所有可查看的字段。 变灰的字段是GC和Memory Visualizer寻找但在当前日志中找不到的字段。 如果未选择“摘要”字段,则可以选择它以启用表格摘要。 同样,您可以启用“调整建议”以获取建议。
图6. VGC Data菜单
比较几个文件
GC和Memory Visualizer可让您并排分析多个文件。 这对于评估性能变化的影响可能非常方便。 图7显示了使用三个GC策略执行固定工作负载的应用程序。 (实际上,应用程序是GC和Memory Visualizer本身。)实线是gencon GC策略,虚线是optavgpause策略,虚线是optthruput策略。 GC和Memory Visualizer会根据日志文件名来设计行标签。 (有关不同类型的GC策略的更多信息,请参阅文章“垃圾收集策略,第1部分”,在提供相关信息 。)
图7.三种不同垃圾收集策略的堆使用和暂停时间
在这种情况下,很明显,gencon模式在所有条件下都是最佳的。 它以最快的速度完成任务,使用更少的进程,并且GC暂停时间大大缩短。 但是gencon策略不是默认策略。 optthruput是因为在大多数情况下,它的性能都优于gencon。 但是,如该示例所示,它并不总是优于gencon策略,因此值得一看的是,当您使用不同的GC策略时,应用程序的行为会如何变化。 通常,非常简单的更改(例如更改应用程序使用的GC策略)可以带来很大的改进。
放大问题时期
通过GC和Memory Visualizer,您可以专注于日志中的特定时间段。 放大特定时间段时,所有摘要数据和建议都会更改以仅反映该时间段。 例如,图8所示的日志显示了白天繁忙但晚上空闲的应用程序的堆使用情况:
图8.白天繁忙而夜间空闲的应用程序的堆使用情况
对于整个日志,GC开销(即执行GC所花费的时间)约为5%,这是相当不错的。 但是,这包括应用程序长时间不进行任何工作且不需要GC的长时间。 放大特定时间段可以更准确地反映系统在繁忙时间段内的行为,如图9所示:
图9.放大繁忙时段
GC和Memory Visualizer也使您可以专注于特定范围的数据。 例如,您可能只对真正的长时间暂停或堆大于500MB的时间感兴趣。 您可以通过更改Y轴上的值来进行这种过滤。
更改单位
GC和Memory Visualizer允许更改显示单位。 更改单位将更改事物的绘制方式,还将更改汇总表和调整建议中的单位。 要更改单位,请右键单击绘图中的单位,或调出“高级透视图”(从“ 视图”菜单中)。
默认情况下,时间(X轴上的单位)以秒为单位显示。 这对于短期运行很方便,但是对于涵盖较长时间段的日志而言并不是很理想。 要更改为其他单位,请从右侧的下拉菜单中选择您喜欢的单位,如图10所示。可能性包括小时,分钟,日期和GC号,这只是集合的序列号。 。 “ 规范化”复选框确定是显示相对于日志开始的时间(规范化)还是绝对时间(未规范化)。
图10.更改单位
您还可以更改Y轴上的单位。 例如,您可以将堆中的数量(默认情况下以兆字节显示)更改为千兆字节或占总堆的百分比。
使用和导出模板
通常,您会发现自己反复查看相同的字段组合。 在GC和Memory Visualizer中, 模板使您可以保存这些组合以供以后使用。 Templates视图位于窗口的左上角,如图11所示:
图11.模板视图
双击模板可将其应用于当前数据集。 GC和Memory Visualizer附带了一些预定义的模板。 堆模板对于评估内存使用情况和应用程序需求很有用。 暂停模板是诊断您可能怀疑与GC相关的性能问题的第一步。
您可以通过调出“视图”菜单或右键单击“模板”视图中的任意位置,然后选择“ 将当前设置导出为模板”来导出模板。 输入模板名称,GC和Memory Visualizer会将模板保存在“模板”视图中,以备将来使用。
改变颜色
您可以选择GC和Memory Visualizer用于绘图的颜色。 单击“视图”菜单中的“首选项”项,然后导航到“显示器”类别中的“显示颜色”页面,如图12所示:
图12.颜色首选项页面
保存输出
您可以通过右键单击主面板并从结果上下文菜单中选择“ 保存”来保存 GC和Memory Visualizer的所有输出,如图13所示。折线图可以保存为JPEG图像,报告可以保存为JPEG图像。保存为HTML,原始数据可以保存为CSV文件。 本文中的图形是从折线图视图中保存的。
图13.保存
使用建议
GC和Memory Visualizer提供了详细GC日志有趣功能的摘要及其调整建议。 摘要和建议位于“报告”选项卡上的报告中。
为什么您会发现有必要进行干预并进行一些手动调整? 垃圾收集器已经进行了大量的自动调整,以尝试优化其性能。 但是,如果没有一些指导,它将无法知道您的优先事项是什么,以及您愿意做出哪些取舍。 没有针对所有工作负载和所有情况的最佳配置。 您可以做的最简单的调整就是指定一个策略,并告诉垃圾回收器吞吐量还是暂停时间最重要。 如果您更喜欢冒险或更渴望获得最佳性能,则可以尝试固定堆大小,更改托儿所大小或尝试更大的最大托儿所大小。
案例研究:诊断内存泄漏
查看详细GC日志的主要原因之一是检查应用程序的内存使用情况,并确保该应用程序在某种程度上没有问题。 例如,一个应用程序可能使用比预期更多的内存,并且详细的GC输出可以提供有关应用程序占用空间的指示。 内存泄漏是一个相关但更严重的问题。 Java平台的GC设施可确保Java应用程序不会丢失内存,即使它在丢失对它的所有引用之前没有释放对象也是如此。 但是,如果应用程序错误地保留了对象引用,它们仍然容易受到泄漏的影响,因为垃圾收集器不会收集仍被引用的对象。
诊断内存泄漏通常非常简单。 在应用程序上启用详细GC,运行一段时间,然后在GC和Memory Visualizer中绘制“使用过的”堆(收集后)。 当应用程序初始化以及应用程序的工作量增加时,应用程序的内存使用量自然会增加。 如果没有明显的理由增加应用程序的内存需求,而使使用的堆线逐渐增加,则可能存在泄漏。 GC和Memory Visualizer会寻找此模式,并在检测到可能泄漏的内容时在调整建议中添加注释。
虽然详细的GC可以向您显示泄漏正在进行中,但是它无法告诉您导致泄漏的对象。 在某些情况下,代码检查将足以找到它。 考虑哈希图和其他集合。 它们是静态的吗? 它们都有删除和添加对象的机制吗? 应用程序在缓存内容方面是否过于慷慨? 对象池也可能是内存泄漏的原因。
如下例所示,弱引用和软引用是修复内存泄漏的强大工具。 (请参阅随附的侧栏关于他们的详细信息相关主题的链接,何时以及如何使用它们的讨论。)考虑清单1.它增加了一个图中显示的明确泄漏应用程序,但从来没有修剪它。
清单1.一个严重泄漏内存的Java类
public class Leaker
{
private Map things = new HashMap();
public void leak() {
while (true) {
things.put(new Date(), new Leak());
}
}
private class Leak
{
private Object data;
public Leak() {
data = new Object();
}
}
}
图14显示了此应用程序的堆使用情况。 堆使用率的下降标志着堆被压缩的点。 JVM内存不足时,日志结束。
图14.极度泄漏的应用程序的堆使用情况
使用弱引用以避免泄漏
如清单2所示,切换到WeakHashMap
可以立即解决该问题; 新的改进的堆使用情况如图15所示。堆使用情况从未超过1MB,应用程序可以无限期地继续运行。
清单2.对Leaker类的简单修正,防止内存泄漏
private Map things = new WeakHashMap();
图15. WeakHashMap挽救了可能泄漏的应用程序
但是,即使参考力弱,也不足以纠正某些泄漏。 如果图15中的地图是一个链表,如清单3所示,该怎么办?
清单3.对Leaker类的进一步修改,重新引入了泄漏
public class Leaker
{
private Map things = new WeakHashMap();
public void leak() {
Object previousThing = null;
while (true) {
final Leak thing = new Leak(previousThing);
things.put(new Date(), thing);
previousThing = thing;
}
}
private class Leak
{
private Object data;
public Leak(Object thing) {
/* Make a linked list */
data = thing;
}
}
}
弱引用告诉垃圾收集器,如果除了弱引用之外没有其他引用,则它应该收集对象。 由于映射中的每个对象都拥有对先前对象的引用,因此不会清除任何弱引用,并且应用程序将很快耗尽内存,如图16所示:
图16. WeakHashMap无法帮助的泄漏应用程序中的堆使用情况
确保弱引用按预期工作
可以通过绘制在GC和Memory Visualizer中清除的弱引用的图形来验证问题,如图17所示。在将链接添加到列表之后,清除的弱引用的数量从大量变为根本没有。 (该应用程序的新版本是沿X轴零位置的极短线,而以前的版本是较长的较高的线。)显然,弱引用不再起作用。
图17.在潜在泄漏的应用程序的两个变体中清除了弱引用
在这种情况下,解决方案是将链接列表中的链接也更改为弱引用。 一旦实现了清单4中所示的代码更改,弱引用的数量就会大大增加,并且堆使用率将恢复为最小:
清单4.引入更多弱引用会阻止引用的保存时间超过要求的时间
private class Leak
{
private WeakReference reference;
public Leak(Object thing) {
this.reference = new WeakReference(thing);
/*
* We can get back our object from the reference with
* reference.get(), but we should always check it for null.
*/
}
}
通过使用GC和Memory Visualizer查看清除的弱引用有多少,您可以轻松地验证重新设计以使用弱引用实际上是有效的。
如果代码检查不能快速发现泄漏,则可能需要进行一些应用程序转储并进行分析,以找到其引用大小不断增加的对象。 请参阅相关主题查找并纠正内存泄漏的文章。
详细的GC日志还可以帮助评估应用程序的可伸缩性。 例如,如果某个应用程序打算处理大量数据,但在测试过程中处理少量数据时使用了大量内存,则该应用程序可能无法按预期扩展。
案例研究:调整堆大小
许多开发人员使用详细的GC数据来帮助选择堆的最佳大小。 如果堆非常小,以致应用程序所需的数据无法容纳其中,则应用程序将耗尽内存并以OutOfMemoryError
终止。 如果堆具有用于应用程序数据的空间,但没有太多剩余空间,则垃圾收集器将不得不花费大量时间来确保堆中有空间用于新分配,这会损害应用程序性能。 通常,太大的堆不会对应用程序性能产生负面影响,但是这很浪费,GC暂停可能很长。 通常,同一台计算机上还运行着其他应用程序,因此重新分配内存可能很有意义,因此没有一个Java应用程序具有比其所需更多的功能。
垃圾收集器将尝试适当地调整堆大小,但是它将避免使用机器上可用物理内存的一半以上。 将堆增加到最佳大小可能还需要一些时间,如果应用程序占用率下降,则可能会缩小堆。 堆大小的这些波动可能会使应用程序变慢,并且如果同一系统上运行的其他任何事物都不需要物理内存,则这是不必要的。 如果很好地了解了应用程序的内存需求,则固定堆大小是一项容易的性能优化。
堆没有单个理想大小。 通常,堆越大,应用程序的性能越好,因此,调整堆大小需要权衡应用程序的需求与其他对物理内存的需求。 合理的试探法是,堆的大小至少应为实时数据量的两倍。 如果不可能或不希望使堆变大,则gencon策略可能是一个不错的选择,因为在堆大小受限制的情况下,gencon策略往往胜过optthruput策略。 尽可能不要对堆进行大小调整,以使计算机需要使用虚拟内存来容纳它。 使用虚拟内存会严重降低性能。
固定堆大小的优点
图18显示了在具有固定大小为500MB的堆大小并使用命令行选项-Xms500m -Xmx500m
的JVM中运行图7中相同的工作负载时产生的暂停时间。 仅基于暂停时间,固定堆大小似乎使情况变得更糟。 每个策略的平均暂停时间都增加了,花费在GC上的时间比例没有变化。 但是,总暂停时间(报告视图中表格的第四列)实际上已经大大降低了。 总暂停时间和平均暂停时间似乎不一致,因为小堆中的收集可以很快完成。 当堆大小可变时,JVM会在堆很小的情况下执行许多非常快速的收集,这些收集会导致平均暂停时间较短。 在这种情况下,最终的性能指标是JVM完成工作所需的时间(行的长度)。 修复堆后,gencon的性能提高了13%,optavgpause的性能提高了15%,optthruput策略的性能提高了30%。
图18.在固定大小的堆中运行的应用程序的暂停时间
当然,此示例适用于执行30秒以上的Java程序,其中JVM花费了大量初始时间来寻找最佳堆大小。 对于运行时间较长的程序,固定堆大小通常不会带来如此大的改进。 如果没有很好地了解工作负载,则固定堆大小可能不是明智的选择,因为可能会迫使JVM在太小的堆中运行。 详细的GC输出可用于评估工作负载的稳定性,以及JVM需要比允许的内存更多的风险。
案例研究:根据详细的GC日志估算应用程序吞吐量
您调整GC以优化应用程序性能。 但是,您如何确定应用程序的性能呢? 基准具有明确的性能指标,但是调整垃圾收集器以优化基准,然后假设相同的配置将为不同的应用程序提供最佳结果是极其不明智的。 所有应用程序都不同,并且垃圾回收器没有最佳的配置。 (如果有的话,垃圾收集器将附带该配置,并且不需要进行调整。)与基准测试不同,并非所有应用程序都提供报告来显示其执行情况。
在这些情况下,冗长的GC日志本身可以很好地提示运行情况。 尽管详细的GC日志是开始评估应用程序性能的好地方,但是报告的暂停时间绝对不是开始的正确位置。 如上面修复堆的示例所示,应用程序有时运行速度可能比调优之前要快,但仍会以不变的GC开销或更长的平均暂停时间为标志。 花大量时间进行GC的应用程序肯定会遇到性能下降的问题,但是由于对象的排列方式,花更多时间在GC上有时会使应用程序的性能更好。
而不是尝试计算GC暂停时间对应用程序的影响,而是查看产生的垃圾量。 应用程序性能的最佳指标之一是应用程序产生了多少垃圾:它生成的垃圾越多,它必须执行的工作就越多,因为垃圾是应用程序工作的副作用。 所有生成的垃圾都会被收集,因此生成的数量与垃圾收集器正在收集的数量完全相同。
您可以通过从GC和Memory Visualizer的VGC数据菜单中选择释放的数量来绘制收集的垃圾量 。 “报告”选项卡上的图显示了有关运行期间收集的垃圾的平均值和总量的统计信息。 平均释放量不是一个好的性能指标; 如果占用率稳定,则每个集合的释放量也可能非常稳定。 但是,如果应用程序运行良好,随着应用程序在更短的时间内完成更多工作,收集的频率可能会增加。 因此,释放的总量是固定时间段内性能的更好指标。 如果您的日志不是在固定时间段内收集的,则放大设置的时间段将确保仅在该时间段内显示总计。
更好的指标是GC的速率,因为即使您要比较的时间不相同,它仍然有意义。 该速率显示在“报告”选项卡顶部的表中。 (如果未显示任何表,请尝试在VGC数据菜单中启用“ 摘要 ”。)具有较高的GC速率意味着您的应用程序可以在较短的时间内完成更多工作-这是一件好事!
考虑前面的固定堆示例。 平均暂停时间给人以GC效果很好的印象。 但是,如果您查看GC的速率,您会发现固定堆运行的GC速率更高。 例如,在比较图19中所示的两个optthruput运行时,如果预先设置了堆大小,则速率要高出12%:
图19.收款率的摘要视图
您还可以将GC的速率视为垃圾生成的速率。 乍一看,垃圾生成似乎是一件坏事,应将其最小化。 确实,与产生较少垃圾的应用程序相比,产生大量垃圾的应用程序的性能可能很差,因为它对垃圾收集器造成了更大的压力-但这并非总是如此。 例如,对象池减少了应用程序生成的垃圾量,但会严重损害垃圾收集性能。 (参见城市性能传奇文章相关主题进行的为什么是这样的情况下进行更多的讨论。)更一般地,抱着一种可能被丢弃减少产生的垃圾量,但往往伤害GC对象引用。 如果适当地限制变量的范围并减少实例变量的使用,则可以减少这种对象保留。
如果应用程序负载不足(也就是说,如果它没有足够的工作要做),则GC的速率就不是一个很好的性能指标,因为如果没有任何工作进入,GC的速率就会下降。例如,服务器如果所有客户端都断开连接,则不会产生太多垃圾,但这并不意味着需要调整服务器。 好消息是,如果应用程序负载不足,则可能不需要太多调整。 如果目的是加快单个事务,那么在事务处理期间放大GC日志将提供适当的信息。
估计应用程序响应时间
如果您更关心应用程序响应时间而不是应用程序吞吐量,该怎么办? 诱人的是,假定冗长的GC暂停时间是应用程序响应时间的良好指标。 但这只是有时候是正确的,甚至只有一半是正确的,因此您需要非常谨慎地从暂停时间推断出什么。 如果应用程序负载不足,则最大暂停时间将与最大响应时间相关。 但是, 平均响应时间通常与吞吐量成正比。 因此,与具有短暂停时间(例如optavgpause)的策略相比,具有较长暂停时间(例如optthruput)的策略实际上可能会提供较低的平均响应时间。 如果应用程序超载,则暂停时间就不再重要,因为工作可能必须排队等待服务,并且响应时间可能主要取决于队列的长度,而队列的长度将取决于应用程序的吞吐量。
结论
如果您努力查看详细的GC日志,则通常会对更好地了解您的应用程序特征有所收获。 您还可以检测到应用程序的内存使用情况中潜在的严重问题,并提高性能。 IBM Java监视和诊断工具-垃圾收集和内存可视化器是一个功能强大的工具,可充分利用详细GC中可用的信息。
翻译自: https://www.ibm.com/developerworks/java/library/j-ibmtools2/index.html