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開(kāi)發(fā)中, 想要進(jìn)行內(nèi)存分析, 總會(huì)看見(jiàn)Shallow Size
和Retained Size
, 這邊文章主要解釋
- 它們分別表示什么含義
- 它們是如何計(jì)算出來(lái)的
Java garbage collection (GC)
我們先了解GC
的一些基本知識(shí)
- 程序中存在一些實(shí)例, 稱作
GC root
, 它們不會(huì)被GC
回收, 常見(jiàn)的例如靜態(tài)變量, 線程等 - 被
GC root
直接或間接引用的實(shí)例會(huì)被標(biāo)記為in use
, 它們也不會(huì)被GC
回收
Shallow Size
Shallow Size
是指實(shí)例自身占用的內(nèi)存, 可以理解為保存該'數(shù)據(jù)結(jié)構(gòu)'需要多少內(nèi)存, 注意不包括它引用的其他實(shí)例
計(jì)算公式:
Shallow Size = [類定義] + 父類fields所占空間 + 自身fields所占空間 + [alignment]
-
類定義
是指, 聲明一個(gè)類本身所需的空間, 固定為8byte, 也就是說(shuō), 一個(gè)不包含任何fields的類的'空類', 也需要占8byte; 另外類定義空間不會(huì)重復(fù)計(jì)算, 就是說(shuō), 即使類繼承其他類, 也只算8byte -
父類fields所占空間
, 對(duì)于繼承了其他類的類來(lái)說(shuō), 父類聲明的fields顯然需要占用一定的空間 -
自身fields所占空間
, 所有fields所占空間之和; fields分基本類型和引用, 基本類型所占空間和系統(tǒng)有關(guān), 例如在32位系統(tǒng)中int=4byte, 64位系統(tǒng)中int=8byte; 引用固定占4byte, 例如String name;
這個(gè)變量聲明占4byte. -
alignment
是指位數(shù)對(duì)齊, 會(huì)讓總空間為8的倍數(shù), 例如某個(gè)A類, 以上3項(xiàng)計(jì)算出來(lái)為15byte, 那么為了對(duì)齊, 讓它是8的倍數(shù), 會(huì)取最接近的值, 所以它的Shallow Size是16byte;
注意,
alignment
行為和JVM有關(guān), 對(duì)于Android來(lái)說(shuō), 實(shí)測(cè)4.4系統(tǒng)會(huì)有對(duì)齊行為, 但是5.1系統(tǒng)不會(huì)
Shallow Size例子
class X {
int a;
byte b;
Integer c = new Integer();
}
假設(shè)當(dāng)前是在32位系統(tǒng), 對(duì)于類X來(lái)說(shuō), 一個(gè)X實(shí)例的Shallow Size為:
- 類定義的8byte
- 沒(méi)有繼承其他類, 所以沒(méi)有父類fields
- a變量為int型, 4byte; b變量為byte型, 1byte; c變量是引用類型, 和它是否指向具體實(shí)例無(wú)關(guān), 固定占4byte
如果不算alignment
,
X的Shallow Size = 8 + 0 + 4 + 1 + 4 = 17byte
如果算上alignment
, 那么要補(bǔ)齊為8的倍數(shù), 也就是24byte.
class Y extends X {
List d;
Date e;
}
一個(gè)Y實(shí)例的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
, 那么要補(bǔ)齊為8的倍數(shù), 也就是32byte.
Retained Size
實(shí)例A的
Retained Size
是指, 當(dāng)實(shí)例A被回收時(shí), 可以同時(shí)被回收的實(shí)例的Shallow Size之和
所以進(jìn)行內(nèi)存分析時(shí), 我們應(yīng)該重點(diǎn)關(guān)注Retained Size較大的實(shí)例; 或者可以通過(guò)Retained Size判斷出某A實(shí)例內(nèi)部使用的實(shí)例是否被其他實(shí)例引用.
例如在Android中, 如果某個(gè)Bitmap
實(shí)例的Retained Size很小, 證明它內(nèi)部的byte數(shù)組被復(fù)用了, 有另一個(gè)Bitmap
實(shí)例指向了同一個(gè)byte數(shù)組.
Retained Size例子
圖中A, B, C, D四個(gè)實(shí)例, 為了方便計(jì)算, 我們假設(shè)所有實(shí)例的Shallow Size都是1kb
D實(shí)例
D實(shí)例沒(méi)有引用其他實(shí)例, 所以移除D實(shí)例只會(huì)釋放它自己的空間, 因此
D實(shí)例的Retained Size=Shallow Size=1kb
C實(shí)例
當(dāng)我們移除C實(shí)例, C實(shí)例引用了D實(shí)例, 同時(shí)D實(shí)例沒(méi)有被其他實(shí)例引用, 所以D實(shí)例也會(huì)被GC, 所以
C實(shí)例的Retained Size = C實(shí)例的Shallow Size + D實(shí)例的Shallow Size = 2kb
B實(shí)例
當(dāng)我們移除B實(shí)例, 雖然B實(shí)例引用了C實(shí)例, 但是A實(shí)例也引用了C實(shí)例, 所以移除B實(shí)例不會(huì)讓C實(shí)例被GC, 所以
B實(shí)例的Retained Size=Shallow Size=1kb
A實(shí)例
當(dāng)我們移除A實(shí)例, 顯然A, B, C, D實(shí)例都會(huì)被GC, 所以
A實(shí)例的Retained Size=4kb
總結(jié)
計(jì)算Retained Size的關(guān)鍵在于領(lǐng)會(huì)移除實(shí)例時(shí), 可以同時(shí)被回收的實(shí)例
, 重點(diǎn)觀察B實(shí)例的情況