Memory for nothing
介绍
之前博客(参考文献2)写过如何找内存泄露的通用方法【大体的步骤是 看dominator tree,如果dominator tree 里面看不出过大的对象,则使用dominator tree 中的group by class 来看】,这篇博客将会介绍Java中的“浪费”堆内存。
在作者日常工作研究实时系统heap dump的分析工作中发现: 有很多collection 实例化了,但是没有使用。collection 在应用系统中很常用。collection 会提前预留一些内存,以备后面存储元素,预留操作使得这看起来有点奢侈。经常能看到成百上千个分配了数兆空间的集合创建之后不再使用。这些空集合预先分配内存,但是很难找出他们。本文将会介绍如何利用MAT找到他们。
一个简单的例子
看一下下面MyValueStorage 类的代码。它定义了三个变量,并且它们会立马分配一个ArrayList 实例。但是只有standardValue 是必须的。specialValues 使用率大约10%,erroneousValues 仅仅在某些特定的情况下才使用。
一个空ArrayList 创建的时候默认10个容量,在32位系统中需要80bytes,在64位系统中需要144bytes.
在MyValueStorage 类中,在堆中有500个实例,如果是32位系统,将会占用80M(如果是64位,则占用144M)来存储specialValues 和 erroneousValuss. 但是仅有大约5%是用到的。所以针对这两个变量一个比较好的办法是延迟初始化(只有在真正使用它们的时候才分配,不用的话可以设置为null)。 需要花费的代价是增加几个if判断,以避免程序运行时出现NullPointerExceptions。
另外,如果仅有几个类的实例,这个改动可能就没有必要了。所以在优化这种情况时,需要明确有多少内存会被占用。
如何发现未使用的集合
为了找到已分配但未使用的集合,可以做如下几步:
- 程序正常运行,获取heap dump
- 使用MAT 中的 OQL (object query language)找出那些大小为0或者modification 数量为0 的集合,比如某些集合没有元素,并且之前从来没有被修改过。选择QOL按钮。 看一下帮主页面对OQL的描述。找下我们需要的几个命令,有关找出空并且未修改的ArrayList HashMaps 以及Hashtable 的几个指令:
select * from java.util.ArrayList where size=0 and modCount=0
select * from java.util.HashMap where size=0 and modCount=0
select * from java.util.Hashtable where count=0 and modCount=0
空集合占用了多少内存?
OQL查询列出了一些满足语法的对象集合。为了找出占用了多少内存,可以从对象列表跳转到直方图里面。
如果要计算这些对象的大小,可以跳转到对应的histogram中去。
是谁维持了这些内存中的空集合?
在我们找出了未使用的空集合列表,然后让我们找一下谁在持有他们?一个比较方便的方法是 右键点击找出的对象,然后使用“Immediate Dominators” 按钮。
也可以使用outgoing and incoming references
这个按钮的用法可以参考这篇文章: https://blogs.sap.com/?p=44312
如下是一个“Immediate Dominators” 的输出案例
可以看到有499,500个myValueStorage 类的实例,占有949.500个未使用的arraylist.
一旦找到这个信息就可以很容易的找到谁分配了这些空集合。
按照上述步骤,会发现很多优化点的。
参考文献
1、原文地址: https://blogs.sap.com/2007/08/02/memory-for-nothing/
2、使用MAT发现内存泄露: https://blogs.sap.com/2007/07/02/finding-memory-leaks-with-sap-memory-analyzer/