了解Android 垃圾回收
判斷對(duì)象是否可以被回收
垃圾收集算法
內(nèi)存分配與回收策略
GC頻繁原因
Memory Churn內(nèi)存抖動(dòng)冬阳,內(nèi)存抖動(dòng)是因?yàn)榇罅康膶?duì)象被創(chuàng)建又在短時(shí)間內(nèi)馬上被釋放蹂随。瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值滨砍,剩余空間不夠的時(shí)候,就會(huì)觸發(fā)GC。即使每次分配的對(duì)象占用了很少的內(nèi)存瘩例,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC甸各。
對(duì)象復(fù)用
如果我們發(fā)現(xiàn)了大量臨時(shí)對(duì)象的創(chuàng)建該如何處理呢垛贤?
首先確定問(wèn)題發(fā)生的原因,對(duì)象頻繁創(chuàng)建一般有以下幾種可能:
- 在循環(huán)操作中創(chuàng)建對(duì)象
- 在頻繁被調(diào)用的方法中創(chuàng)建對(duì)象
例如:在onDraw中使用一些對(duì)象趣倾,由于onDraw方法會(huì)被程序頻繁調(diào)用聘惦,所以我們不能在onDraw方法里面創(chuàng)建對(duì)象實(shí)例,我們可以考慮在onDraw方法外提前初始化這些對(duì)象儒恋。
能直接避免對(duì)象的頻繁創(chuàng)建當(dāng)然最好善绎,但是有時(shí)候這些對(duì)象的初始化是不可避免的,那么我們就要考慮對(duì)象的復(fù)用诫尽,采用對(duì)象池來(lái)解決問(wèn)題禀酱。
對(duì)象池工作過(guò)程
- Pool 表示的是對(duì)象池,用于存儲(chǔ)可重復(fù)利用的對(duì)象
- 第二步操作就是取出對(duì)象牧嫉。
- 第三步操作是使用取出的對(duì)象來(lái)完成一些任務(wù)剂跟。這時(shí)候并不需要對(duì)象池再做什么,但是這也意味著該對(duì)象將被租借一段時(shí)間并且不能在被其他組件借出酣藻。
- 第四步操作就是歸還曹洽,組件歸還借出的對(duì)象這樣可以繼續(xù)滿足其他的租借請(qǐng)求。
在一個(gè)多線程的應(yīng)用中臊恋,第二衣洁,第三,第四步操作都有可能發(fā)生并發(fā)操作抖仅。多線程的組件中分享對(duì)象導(dǎo)致了潛在的并發(fā)問(wèn)題坊夫。也存在一種情況就是當(dāng)所有對(duì)象都被借出時(shí)不能滿足接下來(lái)的請(qǐng)求,對(duì)象池必須應(yīng)對(duì)這些請(qǐng)求撤卢,不管是告訴組件已經(jīng)沒(méi)有對(duì)象可借還是允許組件等待直到有歸還的對(duì)象环凿。
Android官方對(duì)象池源碼解析(見(jiàn)注釋說(shuō)明)
public final class Pools {
public interface Pool<T> {
/**
* @return 從對(duì)象池取出對(duì)象
*/
@Nullable
T acquire();
/**
* 釋放對(duì)象并放入對(duì)象池
*
* @return true表示釋放的對(duì)象成功放入對(duì)象池
*
* @throws IllegalStateException 如果對(duì)象已經(jīng)存在于對(duì)象池中拋異常
*/
boolean release(@NonNull T instance);
}
private Pools() {
/* do nothing - hiding constructor */
}
/**
* 對(duì)象池的非同步實(shí)現(xiàn)
*/
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool; //對(duì)象池中真正用于存儲(chǔ)對(duì)象的數(shù)組
private int mPoolSize; //對(duì)象池內(nèi)的對(duì)象個(gè)數(shù)
/**
* Creates a new instance.
*
* @param maxPoolSize 對(duì)象池最大容量
*/
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize]; //初始化對(duì)象數(shù)組
}
//從mPool數(shù)組中取出mPoolSize - 1位置上的對(duì)象
@Override
@SuppressWarnings("unchecked")
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}
//回收的對(duì)象放入mPool數(shù)組的mPoolSize 位置上
@Override
public boolean release(@NonNull T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}
//判斷對(duì)象是否已存在于對(duì)象池中
private boolean isInPool(@NonNull T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}
/**
* 對(duì)象池的同步實(shí)現(xiàn)
*
* @param <T> The pooled type.
*/
public static class SynchronizedPool<T> extends SimplePool<T> {
private final Object mLock = new Object(); //用于同步加鎖的對(duì)象
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SynchronizedPool(int maxPoolSize) {
super(maxPoolSize);
}
@Override
public T acquire() {
synchronized (mLock) {
return super.acquire();
}
}
@Override
public boolean release(@NonNull T element) {
synchronized (mLock) {
return super.release(element);
}
}
}
}
優(yōu)點(diǎn)
- 復(fù)用對(duì)象池中的對(duì)象,可以避免頻繁創(chuàng)建和銷毀堆中的對(duì)放吩, 進(jìn)而減少垃圾收集器的負(fù)擔(dān)智听, 減少內(nèi)存抖動(dòng);
- 不必重復(fù)初始化對(duì)象狀態(tài), 對(duì)于比較耗時(shí)的constructor和finalize來(lái)說(shuō)非常合適到推;
缺點(diǎn)
- Java的對(duì)象分配操作不比c語(yǔ)言的malloc調(diào)用慢考赛, 對(duì)于輕中量級(jí)的對(duì)象, 分配/釋放對(duì)象的開(kāi)銷可以忽略不計(jì)莉测;
- 并發(fā)環(huán)境中颜骤, 多個(gè)線程可能(同時(shí))需要獲取池中對(duì)象, 進(jìn)而需要在堆數(shù)據(jù)結(jié)構(gòu)上進(jìn)行同步或者因?yàn)殒i競(jìng)爭(zhēng)而產(chǎn)生阻塞捣卤, 這種開(kāi)銷要比創(chuàng)建銷毀對(duì)象的開(kāi)銷高數(shù)百倍忍抽;
- 由于池中對(duì)象的數(shù)量有限, 勢(shì)必成為一個(gè)可伸縮性瓶頸董朝;
- 很難正確的設(shè)定對(duì)象池的大小鸠项, 如果太小則起不到作用, 如果過(guò)大子姜, 則占用內(nèi)存資源高祟绊, 可以開(kāi)啟一個(gè)線程定期掃描分析, 將對(duì)象池壓縮到一個(gè)合適的尺寸以節(jié)約內(nèi)存哥捕,但為了獲得不錯(cuò)的分析結(jié)果久免, 在掃描期間可能需要暫停復(fù)用以避免干擾(造成效率低下), 或者使用非常復(fù)雜的算法策略(增加維護(hù)難度)扭弧;
- 設(shè)計(jì)和使用對(duì)象池容易出錯(cuò)阎姥, 設(shè)計(jì)上需要注意狀態(tài)同步, 這是個(gè)難點(diǎn)鸽捻, 使用上可能存在忘記歸還(就像c語(yǔ)言編程忘記free一樣)呼巴, 重復(fù)歸還(可能需要做個(gè)循環(huán)判斷一下是否池中存在此對(duì)象, 這也是個(gè)開(kāi)銷)御蒲, 歸還后仍舊使用對(duì)象(可能造成多個(gè)線程并發(fā)使用一個(gè)對(duì)象的情況)等問(wèn)題衣赶。
對(duì)象池的使用
在源碼中摘取出來(lái)對(duì)象池的使用幫助類:
/**
* Helper class for creating pools of objects. An example use looks like this:
*
* public class MyPooledClass {
*
* private static final SynchronizedPool<MyPooledClass> sPool =
* new SynchronizedPool<MyPooledClass>(10);
*
* public static MyPooledClass obtain() {
* MyPooledClass instance = sPool.acquire();
* return (instance != null) ? instance : new MyPooledClass();
* }
*
* public void recycle() {
* // Clear state if needed.
* sPool.release(this);
* }
*
* }
* /