前言

在内存快照分析中,想要进行内存分析,总会看见 Shallow SizeRetained Size, 这篇文章主要解释

  1. 它们分别表示什么含义

  2. 它们是如何计算出来的

Java garbage collection (GC)

我们先了解 GC 的一些基本知识

  1. 程序中存在一些实例,称作 GC root, 它们不会被 GC 回收,常见的例如静态变量,线程等

  2. GC root直接或间接引用的实例会被标记为 in use, 它们也不会被 GC 回收

Shallow Size

Shallow Size 是指实例自身占用的内存,可以理解为保存该’数据结构’需要多少内存,注意不包括它引用的其他实例

计算公式:

Code
1
Shallow Size = [类定义] + 父类fields所占空间 + 自身fields所占空间 + [alignment]
  1. 类定义是指,声明一个类本身所需的空间,固定为 8byte, 也就是说,一个不包含任何 fields 的类的’空类’, 也需要占 8byte; 另外类定义空间不会重复计算,就是说,即使类继承其他类,也只算 8byte
  2. 父类fields所占空间 , 对于继承了其他类的类来说,父类声明的 fields 显然需要占用一定的空间
  3. 自身fields所占空间 , 所有 fields 所占空间之和;fields 分基本类型和引用,基本类型所占空间和系统有关,例如在 32 位系统中 int=4byte, 64 位系统中 int=8byte; 引用固定占 4byte, 例如 String name; 这个变量声明占 4byte.
  4. alignment 是指位数对齐,会让总空间为 8 的倍数,例如某个 A 类,以上 3 项计算出来为 15byte, 那么为了对齐,让它是 8 的倍数,会取最接近的值,所以它的 Shallow Size 是 16byte;

注意,alignment 行为和 JVM 有关,对于 Android 来说,实测 4.4 系统会有对齐行为,但是 5.1 系统不会

Shallow Size 例子

java
1
2
3
4
5
class X {
int a;
byte b;
Integer c = new Integer();
}

假设当前是在 32 位系统 , 对于类 X 来说,一个 X 实例的 Shallow Size 为:

  1. 类定义的 8byte
  2. 没有继承其他类,所以没有父类 fields
  3. a 变量为 int 型,4byte; b 变量为 byte 型,1byte; c 变量是引用类型,和它是否指向具体实例无关,固定占 4byte

如果不算alignment,

go
1
X的Shallow Size = 8 + 0 + 4 + 1 + 4 = 17byte

如果算上alignment, 那么要补齐为 8 的倍数,也就是 24byte.

java
1
2
3
4
class Y extends X {
List d;
Date e;
}

一个 Y 实例的 Shallow Size 为:

  1. 类定义的 8byte
  2. 继承了 X 类,X 类的所有 fields 为 X 类的 Shallow Size 减去类定义空间 8byte, 也就是 17byte-8byte=9byte
  3. d, e 都是引用类型,各占 4byte

如果不算alignment,

go
1
Y的Shallow Size = 8 + 9 + 4 + 4 = 25byte

如果算上alignment, 那么要补齐为 8 的倍数,也就是 32byte.

Retained Size

实例 A 的 Retained Size 是指,当实例 A 被回收时,可以同时被回收的实例的 Shallow Size 之和

所以进行内存分析时,我们应该重点关注 Retained Size 较大的实例;或者可以通过 Retained Size 判断出某 A 实例内部使用的实例是否被其他实例引用.
例如在 Android 中,如果某个 Bitmap 实例的 Retained Size 很小,证明它内部的 byte 数组被复用了,有另一个 Bitmap 实例指向了同一个 byte 数组.

Retained Size 例子

Retained Size例子

Retained Size 例子

RetainedSize 例子.png

图中 A, B, C, D 四个实例,为了方便计算,我们假设所有实例的 Shallow Size 都是 1kb

D 实例

D 实例没有引用其他实例,所以移除 D 实例只会释放它自己的空间,因此

Code
1
D实例的Retained Size=Shallow Size=1kb

C 实例

当我们移除 C 实例,C 实例引用了 D 实例,同时 D 实例没有被其他实例引用 , 所以 D 实例也会被 GC, 所以

Code
1
C实例的Retained Size = C实例的Shallow Size + D实例的Shallow Size = 2kb

B 实例

当我们移除 B 实例,虽然 B 实例引用了 C 实例,但是 A 实例也引用了 C 实例,所以移除 B 实例不会让 C 实例被 GC, 所以

Code
1
B实例的Retained Size=Shallow Size=1kb

A 实例

当我们移除 A 实例,显然 A, B, C, D 实例都会被 GC, 所以

Code
1
A实例的Retained Size=4kb

总结

计算 Retained Size 的关键在于领会移除实例时, 可以同时被回收的实例 , 重点观察 B 实例的情况

参考文档

1. How much memory do I need (part 1) – What is retained heap? | Plumbr – User Experience & Application Performance Monitoring

2. How much memory do I need (part 2) – What is shallow heap? | Plumbr – User Experience & Application Performance Monitoring