使用Java Mission Control进行内存分配分析
jdk7u40自带了一个非常好用的工具,就是 Java Mission Control。 JRockit Misson Control用户应该会对mission control的很多功能十分熟悉,JRockit也是一款很棒的工具。本篇文章将着重关注如何使用Java Flight Recorder进行内存分配分析。
jvm有着非常棒的小块内存虚拟化技术,这会让你产生一种拥有无限内存的错觉感,其实它的开销非常大。有时候jvm需要找出此刻堆上数据是如何被使用的,并把剩余的空间扩大——这就是垃圾回收。产生这种情况的原因是,jvm实际获得的物理内存是有限的,因此需要在不被使用时进行内存回收和复用。在一些时间敏感的应用中,比如交易系统和通信程序,这些暂停是不能容忍的。有很多GC调优方法可以避免这种暂停发生。貌似上面的讨论已经跑题了。让GC变少的方法当然是尽量减少分配内存。
有时候,你希望找出在你的程序中哪些地方导致了内存分配的压力。引起这种压力的原因有很多种。最普通的一种情况可能是jvm需要经常GC,并且时间远超过你认为的合理值。
JFR分配事件
在HotSpot7u40中实现的Java Flight Recorder(JFR)有两种内存分配事件可以帮助我们找出程序中进行内存分配的地方:TLAB中的内存分配和TLAB外的内存分配事件。
与JDK提供的其它大多数事件相类似,有一系列可在Mission Control中进行定制的分析接口。在进行分析之前,我们要花上几分钟来讨论下实际的事件。
首先,你需要确保事件已近被记录了。如果你使用默认的模板选项,你会发现内存分配分析选项默认是关闭的。要么打开它,或者使用分析模板而不要使用默认模板。内存分析选项默认关闭的原因是,它可能会产生太多的事件。并且对于不同的程序它的性能消耗是不确定的,因为不同的程序在内存分配的情况上是差别是很大的。
事件标签组中的Log标签是查看每个单独事件的绝佳地址。我们来看看这里包括了哪些内容:
这里包括已经分配的内存确切大小、导致内存分配的堆栈轨迹、分配内存的类和事件信息。在TLAB内存分配事件中,它们还包括分配的TLAB大小。
需要注意的是,TLAB的内存分配事件中并不是对应每一个地点的内存分配事件,那样太消耗资源了。取而代之的是,我们只在新的TLAB中第一次分配内存时进行事件上报。这意味着我们只获得了本地线程的内存分配一些排序后的样本。
此外,需要注意的是我们在JRockit中使用的TLA事件和在新生代中进行的分配是完全相同的,大对象分配事件和老生代内存分配时相同的。不过这些在HotSpot中有点复杂。TLAB外的内存分配事件既可以和年轻代内存回收分配相同,也可以和后续的Eden区及old区域的直接大对象分配相同。换句话说,如果你在TLAB事件外发现了一些少量的内存分配,你并不需要担心。正常来说,这种情况是非常的少见的。因此在实际情况中这些区分并不是那么明显。
使用Allocation标签页
Allocation(分配)标签页实际上包含三个子标签页:
- 通用
- 新TLAB分配
- TLAB外分配
通用标签页提供了一个总的视图和一些可用事件的统计信息,例如对于不同的事件类型分配的总内存。依据你首要目标的不同,对待这些信息的方式也会不同。下面的这个例子十分精简并且聚焦在内部TLAB分配,因为它更关注于总的内存分配压力。
新TLAB分配提供了三种以上专供于分析新TLAB分配事件的虚拟化类型:类级、线程级和分析级的分配。分析级分配是对这三种类型的一个简单的栈追踪集合。从下面的这个例子可以看出,Integer对象几乎占到整个系统的所有内存分配。在直方图中选择Integer类便会显示出对Integer对象的所有内存的栈轨迹集合,同时在这个例子中,所有的分配都来自同一个地点,正如下图展示的那样。
TLAB外分配的内存标签页和新TLAB标签页的工作原理相同,仅仅在这个时间点是一致的,当然这些是TLAB外的事件。
限制
因为新TLAB的内存分配事件仅仅只是总的线程本地内存分配的样本,仅仅只有一个事件是很难说明实际情况的。更多的事件才会有更精确的图片。此外,如果分配事件的行为在记录期间变化非常大,那么这样也很难建立一个十分有说服力本地内存分配视图。