Shallow Size和Retained Size详解
参考文章
How much memory do I need (part 1) – What is retained heap?
How much memory do I need (part 2) – What is shallow heap?
在Android开发中, 想要进行内存分析, 总会看见Shallow Size
和Retained Size
, 这边文章主要解释
- 它们分别表示什么含义
- 它们是如何计算出来的
Java garbage collection (GC)
我们先了解GC
的一些基本知识
- 程序中存在一些实例, 称作
GC root
, 它们不会被GC
回收, 常见的例如静态变量, 线程等 - 被
GC root
直接或间接引用的实例会被标记为in use
, 它们也不会被GC
回收
Shallow Size
Shallow Size
是指实例自身占用的内存, 可以理解为保存该'数据结构'需要多少内存, 注意不包括它引用的其他实例
计算公式:
Shallow Size = [类定义] + 父类fields所占空间 + 自身fields所占空间 + [alignment]
-
类定义
是指, 声明一个类本身所需的空间, 固定为8byte, 也就是说, 一个不包含任何fields的类的'空类', 也需要占8byte; 另外类定义空间不会重复计算, 就是说, 即使类继承其他类, 也只算8byte -
父类fields所占空间
, 对于继承了其他类的类来说, 父类声明的fields显然需要占用一定的空间 -
自身fields所占空间
, 所有fields所占空间之和; fields分基本类型和引用, 基本类型所占空间和系统有关, 例如在32位系统中int=4byte, 64位系统中int=8byte; 引用固定占4byte, 例如String name;
这个变量声明占4byte. -
alignment
是指位数对齐, 会让总空间为8的倍数, 例如某个A类, 以上3项计算出来为15byte, 那么为了对齐, 让它是8的倍数, 会取最接近的值, 所以它的Shallow Size是16byte;
注意,
alignment
行为和JVM有关, 对于Android来说, 实测4.4系统会有对齐行为, 但是5.1系统不会
Shallow Size例子
class X {
int a;
byte b;
Integer c = new Integer();
}
假设当前是在32位系统, 对于类X来说, 一个X实例的Shallow Size为:
- 类定义的8byte
- 没有继承其他类, 所以没有父类fields
- a变量为int型, 4byte; b变量为byte型, 1byte; c变量是引用类型, 和它是否指向具体实例无关, 固定占4byte
如果不算alignment
,
X的Shallow Size = 8 + 0 + 4 + 1 + 4 = 17byte
如果算上alignment
, 那么要补齐为8的倍数, 也就是24byte.
class Y extends X {
List d;
Date e;
}
一个Y实例的Shallow Size为:
- 类定义的8byte
- 继承了X类, X类的所有fields为X类的Shallow Size减去类定义空间8byte, 也就是17byte-8byte=9byte
- d, e都是引用类型, 各占4byte
如果不算alignment
,
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例子
图中A, B, C, D四个实例, 为了方便计算, 我们假设所有实例的Shallow Size都是1kb
D实例
D实例没有引用其他实例, 所以移除D实例只会释放它自己的空间, 因此
D实例的Retained Size=Shallow Size=1kb
C实例
当我们移除C实例, C实例引用了D实例, 同时D实例没有被其他实例引用, 所以D实例也会被GC, 所以
C实例的Retained Size = C实例的Shallow Size + D实例的Shallow Size = 2kb
B实例
当我们移除B实例, 虽然B实例引用了C实例, 但是A实例也引用了C实例, 所以移除B实例不会让C实例被GC, 所以
B实例的Retained Size=Shallow Size=1kb
A实例
当我们移除A实例, 显然A, B, C, D实例都会被GC, 所以
A实例的Retained Size=4kb
总结
计算Retained Size的关键在于领会移除实例时, 可以同时被回收的实例
, 重点观察B实例的情况