在java内存分析软件(mat,jhat等)中,有两个概念是 shallow heap
和 retained heap
(有时候叫shallow size 和 retained size)。
shallow heap
比较好理解(好理解不代表好计算),直译就是浅层堆,其实就是这个对象实际占用的堆大小。
retained heap
比较难理解,直译过来是保留堆,一般会大于或者等于shallow heap,那么retained heap如何理解呢?
retained heap 的计算方法
首先,不能按照 shallow(浅) 和 deep(深)的层次来理解这个retained heap,其实最简单的理解就是,如果这个对象被删除了(GC回收掉),能节省出多少内存,这个值就是所谓的retained heap。而GC算法中,是否回收一个对象,主要是判断一个对象是否存在引用(还有一些系统级别或特定对象不在此列),至于标记还是引用计数算法,最终都是为了判断是否被引用。简单理解,如果一个对象没有被引用了,就可以回收了。
这里我们先定义一下引用(这里不包含所有“引用”的定义,比如数组会引用他的所有元素,所有对象都会引用他的Class对象等等,这里只是为了简单举例):如果一个类的对象出现在另一个类的成员里,那我们就认为后者引用了前者。比如 类 A
和 B
, 其中 B
中有一个成员变量是 A
的对象,那么就说B引用了A。如下代码:
public class A { //class define... } public class B { private A aObj; //rest class define }
下面用 ->
来表示引用
我们假设有四个对象, 假设他们的shallow heap都是 1K, 他们的引用关系如下:
A -> B, A -> C (A同时引用了B和C)
B -> C
C -> D
注意D已经没有引用其他的类了(即使是JDK里面提供的String, Date, Integer等都不行)
很显然, D的 retained heap 等于他的 shallow heap = 1K。
C 引用了 D, 如果把C删了,D也会被回收, 所以 C 的retaned heap = 2K
B 引用了C, 但是把B 删除了之后,C并不会回收,因为还有A在引用C,所以B的retained heap = 1K
A 引用了B和C, 把A删除后,BCD都会被回收,所以A的retained heap = 4K
假设, 我们再添加一个对象(shallow heap也是1K), 他引用了B,会怎样呢?
D的retained heap 还是 1K
C的retained heap 还是 2K
B的retained heap 还是 1K
A的retained heap 变成 1K, 因为删除A,其他的节点并不会被回收
E的retained heap 也是 1K, 因为删除B,其他的节点也并不会被回收
理解了 retained heap,再来理解 shallow heap。上面说过, shallow heap比较好理解,但是不代表他好计算。。。因为他的计算跟
1. CPU架构有关
2. 操作系统位数有关
3. 跟JVM参数有关
shallow heap的计算方法
所以这里就规定,以下的分析和计算是基于32位x86系统的
shallow heap 的是有计算公式的:
shallow heap = 类定义引用大小 + 父类的成员变量所占的空间 + 当前类的成员变量所占的空间 + 结构体对齐
其中类定义引用的大小是固定(注意不同架构下这个定义是不一样的),在32位架构下为4个字节。对象引用变量也是4个字节(也是跟体系架构有关),最后结构体对齐是为了对齐 8字节的倍数。
举个栗子:
class A { int a; byte b; Integer c = new Integer(1); } class B extends A { Date d; String e; List f; }
如果有对象:
A a = new A(); B b = new B();
下面来计算a和b的shallow heap。
a的shallow heap = 8个字节类定义引用 + 4个字节的int成员 + 1个字节的byte成员 + 4个字节的Integer引用成员 + 7个字节的结构体对齐 = 24Byte
(注意这里不用管 new Integer(1), 这是A类引用了Integer类,这份空间会计算在他的retained heap里面。)
b的shallow heap = 8个字节的类定义引用 + 9 个字节的父类成员空间 + 4个字节的Date引用 + 4个字节的String 引用 + 4 个字节的 List 引用 + 3个字节的结构体对齐 = 32B
一个对象的的shallow heap只需要对齐一次,所以这里不需要在父类对齐一次,然后在子类也再对齐一次。
上面之所以要先定义好是在32bit x86架构下,是因为不同架构确实有不同的定义:
64bit JVM的 类定义引用字节是 12Byte
通过设置JVM flag参数和heap的大小,对象引用的大小也可以从4字节变成8字节
另外还有一些架构每个对象都有自己的对齐规则的。
参考文献
https://plumbr.eu/blog/memory-leaks/how-much-memory-what-is-retained-heap
https://plumbr.eu/blog/memory-leaks/how-much-memory-do-i-need-part-2-what-is-shallow-heap
http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fshallowretainedheap.html
深度好文
好文