內(nèi)存抖動(dòng)宜猜、內(nèi)存溢出、內(nèi)存泄漏
- 內(nèi)存抖動(dòng)
在極短的時(shí)間內(nèi)硝逢,分配大量的內(nèi)存姨拥,然后又釋放它,這種現(xiàn)象就會(huì)造成內(nèi)存抖動(dòng)渠鸽。典型地叫乌,在 View 控件的 onDraw 方法里分配大量內(nèi)存,又釋放大量內(nèi)存徽缚,這種做法極易引起內(nèi)存抖動(dòng)憨奸,從而導(dǎo)致性能下降。因?yàn)?onDraw 里的大量內(nèi)存分配和釋放會(huì)給系統(tǒng)堆空間造成壓力凿试,觸發(fā) GC 工作去釋放更多可用內(nèi)存排宰,而 GC 工作起來時(shí),又會(huì)吃掉寶貴的幀時(shí)間 (幀時(shí)間是 16ms) 那婉,最終導(dǎo)致性能問題板甘。GC工作是發(fā)生在主線程中的,因?yàn)轭l繁的觸發(fā)GC導(dǎo)致掉幀就是內(nèi)存抖動(dòng)详炬。 - 內(nèi)存溢出
個(gè)Android應(yīng)用程序都執(zhí)行在自己的虛擬機(jī)中虾啦,每個(gè)虛擬機(jī)必定會(huì)有堆內(nèi)存閾值限制(值得一提的是這個(gè)閾值一般都由廠商依據(jù)硬件配置及設(shè)備特性自己設(shè)定,沒有統(tǒng)一標(biāo)準(zhǔn)痕寓,可以為64M傲醉,也可以為128M等;它的配置是在Android的屬性系統(tǒng)的/system/build.prop中配置dalvik.vm.heapsize=128m即可呻率,若存在dalvik.vm.heapstartsize則表示初始申請大小硬毕,也可以通過ActivityManager.getMemoryClass()獲得這個(gè)值),也即一個(gè)應(yīng)用進(jìn)程同時(shí)存在的對象必須小于閾值規(guī)定的內(nèi)存大小才可以正常運(yùn)行礼仗,否則則會(huì)報(bào)oom(OutOfMemoryError)吐咳。產(chǎn)生的原因最直接的原因是一下子申請大量的內(nèi)存超出閾值直接崩潰逻悠,還有原因是因?yàn)殄e(cuò)誤的程序?qū)е聝?nèi)存泄漏,即使申請一小段內(nèi)存也會(huì)直接崩潰韭脊。 - 內(nèi)存泄漏
內(nèi)存泄漏是本該由GC回收的內(nèi)存因?yàn)槟承┰虻貌坏交厥斩鴮?dǎo)致的童谒,通俗的講是本應(yīng)該被回收的對象被比它生命周期還有長的對象持有,導(dǎo)致不可回收沪羔,就產(chǎn)生了內(nèi)存泄漏饥伊。
舉個(gè)例子一(單例最常見的內(nèi)存泄漏):
public final class MainActivity extends Activity
{
private DbManager mDbManager;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//DbManager是一個(gè)單例模式類,這樣就持有了
//MainActivity引用蔫饰,導(dǎo)致泄露
mDbManager = DbManager.getInstance(this);
}
}
分析:由于單例的靜態(tài)特性使得它的生命周期比較長琅豆,又因?yàn)樗钟衋ctivity,所以當(dāng)activity退出時(shí)篓吁,此activity得不到GC回收從而導(dǎo)致了內(nèi)存泄漏茫因。
舉例二:集合中
Vector v = new Vector(10);
for (int i = 1; i < 100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
分析:
在這個(gè)例子中,我們循環(huán)申請Object對象杖剪,并將所申請的對象放入一個(gè) Vector 中冻押,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對象盛嘿,所以這個(gè)對象對 GC 來說是不可回收的翼雀。因此,如果對象加入到Vector 后孩擂,還必須從 Vector 中刪除狼渊,最簡單的方法就是將 Vector 對象設(shè)置為 null。
Android中常見的內(nèi)存泄漏匯總以及相應(yīng)的解決辦法
- 集合類
如果僅僅有添加元素的方法类垦,而沒有相應(yīng)的刪除機(jī)制狈邑,導(dǎo)致內(nèi)存被占用。如果這個(gè)集合類是全局性的變量 (比如類中的靜態(tài)屬性蚤认,全局性的 map 等即有靜態(tài)引用或 final 一直指向它)米苹,那么沒有相應(yīng)的刪除機(jī)制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減砰琢。比如上面的典型例子就是其中一種情況蘸嘶。 - 單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長,所以如果使用不恰當(dāng)?shù)脑捙闫苋菀自斐蓛?nèi)存泄漏训唱。比如上面的典型例子就是其中一種情況。它的單例一般是這樣
public class AppManager
{
private static AppManager instance;
private Context context;
private AppManager(Context context)
{
this.context = context;
}
public static AppManager getInstance(Context context)
{
if (instance == null)
{
instance = new AppManager(context);
}
return instance;
}
}
分析:這是一個(gè)普通的單例模式挚冤,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候况增,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長短至關(guān)重要训挡,如果此時(shí)傳入的是 Application 的 Context澳骤,因?yàn)锳pplication 的生命周期就是整個(gè)應(yīng)用的生命周期歧强,所以這將沒有任何問題。如果此時(shí)傳入的是 Activity 的 Context为肮,當(dāng)這個(gè) Context 所對應(yīng)的 Activity 退出時(shí)摊册,由于該 Context 的引用被單例對象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期颊艳,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收茅特,這就造成泄漏了。
正確的方式應(yīng)該改為下面這種方式:
public class AppManager
{
private static AppManager instance;
private Context context;
private AppManager(Context context)
{
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context)
{
if (instance == null)
{
instance = new AppManager(context);
}
return instance;
}
}
- 匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程
舉個(gè)例子
public class MainActivity extends Activity
{
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable()
{
@Override
public void run()
{
}
};
...
}
分析:匿名內(nèi)部類是默認(rèn)持有外部的引用籽暇,因此容易造成內(nèi)存泄漏。
- Handler 使用不當(dāng)造成的內(nèi)存泄漏
Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了饭庞,很多時(shí)候我們?yōu)榱吮苊?ANR 而不在主線程進(jìn)行耗時(shí)操作戒悠,在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請求回調(diào)等api都借助Handler來處理,但 Handler 不是萬能的舟山,對于 Handler 的使用代碼編寫一不規(guī)范即有可能造成內(nèi)存泄漏绸狐。另外,我們知道 Handler累盗、Message 和 MessageQueue 都是相互關(guān)聯(lián)在一起的寒矿,萬一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對象將被線程 MessageQueue 一直持有若债。 由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的符相。因此這種實(shí)現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無法正確釋放蠢琳。
舉個(gè)栗子:
public class SampleActivity extends Activity
{
private final Handler mLeakyHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable()
{
@Override
public void run()
{
/* ... */
}
}, 1000 * 60 * 10); // Go back to the previous Activity.
finish();
}
}
分析:
在該 SampleActivity 中聲明了一個(gè)延遲10分鐘執(zhí)行的消息 Message啊终,mLeakyHandler 將其 push 進(jìn)了消息隊(duì)列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時(shí)傲须,延遲執(zhí)行任務(wù)的 Message 還會(huì)繼續(xù)存在于主線程中蓝牲,它持有該 Activity 的 Handler 引用,所以此時(shí) finish() 掉的 Activity 就不會(huì)被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類泰讽,它會(huì)持有外部類的引用例衍,在這里就是指 SampleActivity)。
修復(fù)方法:
在 Activity 中避免使用非靜態(tài)內(nèi)部類已卸,比如上面我們將 Handler 聲明為靜態(tài)的佛玄,則其存活期跟 Activity 的生命周期就無關(guān)了。同時(shí)通過弱引用的方式引入 Activity累澡,避免直接將 Activity 作為 context 傳進(jìn)去翎嫡,見下面代碼:
public class SampleActivity extends Activity
{
/** * Instances of static inner classes do not hold an implicit * reference to their outer class. */
private static class MyHandler extends Handler
{
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity)
{
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg)
{
SampleActivity activity = mActivity.get();
if (activity != null)
{
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */
private static final Runnable sRunnable = new Runnable()
{
@Override
public void run()
{
/* ... */
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity.
finish();
}
}
綜述,即推薦使用靜態(tài)內(nèi)部類 + WeakReference 這種方式永乌。每次使用前注意判空惑申。
資源未關(guān)閉造成的內(nèi)存泄漏
對于使用了BraodcastReceiver具伍,ContentObserver,F(xiàn)ile圈驼,游標(biāo) Cursor人芽,Stream,Bitmap等資源的使用绩脆,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷萤厅,否則這些資源將不會(huì)被回收澄惊,造成內(nèi)存泄漏旱幼。一些不良代碼造成的內(nèi)存壓力
比如:
1 都弹、Bitmap 沒調(diào)用 recycle()方法潘拨,對于 Bitmap 對象在不使用時(shí),我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存陶贼,然后才它設(shè)置為 null. 因?yàn)榧虞d Bitmap 對象的內(nèi)存空間蟀伸,一部分是 java 的狼钮,一部分 C 的(因?yàn)?Bitmap 分配的底層是通過 JNI 調(diào)用的 )县好。 而這個(gè) recyle() 就是針對 C 部分的內(nèi)存釋放主守。
2 禀倔、構(gòu)造 Adapter 時(shí),沒有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView参淫。這里推薦使用 ViewHolder