MAT中的Shallow Heap和Retained Heap

概述

在使用MAT分析内存快照的时候我们一般会关注对象占用的内存大小,在MAT中我们可以观察到两个数据Shallow Heap和Retained Heap,网上有很多文章介绍这两个数据的概念以及计算方式,但是很多文章都是有问题的(因此走了不少弯路),所以自己研究了一下,接下来分别介绍。

PS:以下均以32位为例

Shallow Heap

Shallow Heap指的是对象本身占用的内存大小,包含两部分:

  • 对象头:可参考Java的对象头和对象组成详解,总结一下就是普通对象的对象头占8字节,数组对象占12字节(包括4字节的数组长度)
  • 成员变量:如果是基本类型则按照基本类型的大小来算(如int占用4字节,char占用2字节),如果是对象引用则一律占用4个字节

由以上可得出Shallow Heap计算方法为

1
Shallow Heap = sizeof(对象头) + sizeof(所有成员变量)

由于对象内存分配是以8字节为单位的,所以最终的Shallow Heap如果不是8的倍数则需要增加至8的倍数

Retained Heap

为了能够更好的理解,引入Retained Set 和 Dominator的概念

  • Dominator:如果GCRoot到对象B的所有路径中均包含对象A,则称A为B的Dominator,意味着如果A被回收,GCRoot将没有到B的链路,B成为可回收对象。
  • Retained Set:对象A的Retained Set指的是包含所有以A为Dominator的对象的集合

A的Retained Heap就是A和Retained Set中所有对象的Shallow Heap的合,可以理解为A被回收后进行一次GC,包括A可总共回收的内存就是Retained Heap。

Retained Heap计算方法为

1
Retained Heap = shallowHeapOf(A) + shallowHeapOf(Retained Set内所有对象)

举例

接下来举一个例子来应用上述计算方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A {
int i = 0;
long l = 0;
int[] intArray = new int[2];
char[] charArray = new char[2];
B b = new B();
}

public class B {
int i = 0;
long l = 0;
}

public static void main(String[] args) {
A a = new A();
}

内存中有一个对象A,A持有对象B,我们首先来计算A、B的Shallow Heap

计算Shallow Heap

A持有两个基本类型的变量i、l和三个对象引用(数组也是对象!),所以

Shallow Heap = 8(对象头) + 4(int) + 8(long) + 4(对象引用) + 4(对象引用) + 4(对象引用) = 32

B就只有两个基本类型的变量,所以

Shallow Heap = 8(对象头) + 4(int) + 8(long) = 20

但是20不是8的倍数,所以真正的Shallow Heap为24

计算Retained Heap

由于B没有持有任何对象引用,所以Retained Heap就是Shallow Heap,为24
A持有三个对象引用,所以

Retained Heap(A) = Shallow Heap(A) + 三个对象的Retained Heap

intArray不持有对象,只持有基本类型int,所以

Retained Heap(intArray) = Shallow Heap(intArray) = 12(对象头) + 4(int) * 2 = 20 -> 24

charArray同样不持有对象,只持有基本类型char,所以

Retained Heap(charArray) = Shallow Heap(charArray) = 12(对象头) + 2(char) * 2 = 16

综上

Retained Heap(A) = 32(Shallow Heap) + 24(int[]) + 16(char[]) + 24(b) = 96

参考文章

Shallow and retained heap
Java的对象头和对象组成详解
Immediate Dominators