簡(jiǎn)單介紹內(nèi)存泄漏&內(nèi)存抖動(dòng)
內(nèi)存泄漏:
Memory leak, 是一種資源泄漏,主因是計(jì)算機(jī)程序?qū)Υ鎯?chǔ)器配置管理失當(dāng),失去對(duì)一段已分配內(nèi)存空間的控制,造成程序繼續(xù)占用已經(jīng)不再使用的內(nèi)存空間,或是存儲(chǔ)器所存儲(chǔ)之對(duì)象無(wú)法透過(guò)執(zhí)行代碼而訪問(wèn),令內(nèi)存資源空耗。
簡(jiǎn)單來(lái)說(shuō),內(nèi)存泄漏是指無(wú)法正確回收已經(jīng)不再使用的內(nèi)存。
舉例:
請(qǐng)注意以下的例子是虛構(gòu)的
在此例中的應(yīng)用程序是一個(gè)簡(jiǎn)單軟件的一小部分,用來(lái)控制電梯的運(yùn)作。<br>此部分軟件當(dāng)乘客在電梯內(nèi)按下一樓層的按鈕時(shí)運(yùn)行。
當(dāng)按下按鈕時(shí):
要求使用存儲(chǔ)器,用作記住目的樓層
把目的樓層的數(shù)字儲(chǔ)存到存儲(chǔ)器中
電梯是否已到達(dá)目的樓層?
如是,沒(méi)有任何事需要做:程序完成
否則:
等待直至電梯停止
到達(dá)指定樓層
釋放剛才用作記住目的樓層的存儲(chǔ)器
此程序有一處會(huì)造成存儲(chǔ)器泄漏:如果在電梯所在樓層按下該層的按鈕(即上述程序的第4步),程序?qū)⒂|發(fā)判斷條件而結(jié)束運(yùn)行,但存儲(chǔ)器仍一直被占用而沒(méi)有被釋放。這種情況發(fā)生得越多,泄漏的存儲(chǔ)器也越多。
這個(gè)小錯(cuò)誤不會(huì)造成即時(shí)影響。因?yàn)槿瞬粫?huì)經(jīng)常在電梯所在樓層按下同一層的按鈕。而且在通常情況下,電梯應(yīng)有足夠的存儲(chǔ)器以應(yīng)付上百次、上千次類似的情況。不過(guò),電梯最后仍有可能消耗完所有存儲(chǔ)器。這可能需要數(shù)個(gè)月或是數(shù)年,所以在簡(jiǎn)單的測(cè)試下這個(gè)問(wèn)題不會(huì)被發(fā)現(xiàn)。
而這個(gè)例子導(dǎo)致的后果會(huì)是不那么令人愉快。至少,電梯不會(huì)再理會(huì)前往其他樓層的要求。更嚴(yán)重的是,如果程序需要存儲(chǔ)器去開(kāi)啟電梯門,那可能有人被困電梯內(nèi),因?yàn)殡娞輿](méi)有足夠的存儲(chǔ)器去開(kāi)啟電梯門。
存儲(chǔ)器泄漏只會(huì)在程序運(yùn)行的時(shí)間內(nèi)持續(xù)。例如:關(guān)閉電梯的電源時(shí),程序終止運(yùn)行。當(dāng)電源再度開(kāi)啟,程序會(huì)再次運(yùn)行而存儲(chǔ)器會(huì)重置,而這種緩慢的泄漏則會(huì)從頭開(kāi)始再次發(fā)生。
內(nèi)存抖動(dòng)
源自Android文檔中的Memory churn一詞,中文翻譯為內(nèi)存抖動(dòng)。
指快速頻繁的創(chuàng)建對(duì)象從而產(chǎn)生的性能問(wèn)題。
引用Android文檔原文:
垃圾回收事件通常不會(huì)影響應(yīng)用的性能。不過(guò),如果在短時(shí)間內(nèi)發(fā)生許多垃圾回收事件,就可能會(huì)快速耗盡幀時(shí)間。系統(tǒng)花在垃圾回收上的時(shí)間越多,能夠花在呈現(xiàn)或流式傳輸音頻等其他任務(wù)上的時(shí)間就越少。
通常,“內(nèi)存抖動(dòng)”可能會(huì)導(dǎo)致出現(xiàn)大量的垃圾回收事件。實(shí)際上,內(nèi)存抖動(dòng)可以說(shuō)明在給定時(shí)間內(nèi)出現(xiàn)的已分配臨時(shí)對(duì)象的數(shù)量。
例如,您可以在 `for` 循環(huán)中分配多個(gè)臨時(shí)對(duì)象。或者,您也可以在視圖的 `onDraw()` 函數(shù)中創(chuàng)建新的 `Paint` 或 `Bitmap` 對(duì)象。在這兩種情況下,應(yīng)用都會(huì)快速創(chuàng)建大量對(duì)象。這些操作可以快速消耗新生代 (young generation) 區(qū)域中的所有可用內(nèi)存,從而迫使垃圾回收事件發(fā)生。
內(nèi)存泄漏(Memory leak)的產(chǎn)生和避免方式
JAVA內(nèi)存泄漏的根本原因是長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏。
盡管短生命周期對(duì)象已經(jīng)不再需要,但因?yàn)殚L(zhǎng)生命周期依舊持有它的引用,故不能被回收而導(dǎo)致內(nèi)存泄漏。
幾種引起內(nèi)存泄漏的問(wèn)題:
靜態(tài)集合類引起的內(nèi)存泄漏
HashMap、ArrayList等集合以靜態(tài)形式聲明時(shí),這些靜態(tài)對(duì)象的生命周期與應(yīng)用程序一致。他們所引用的對(duì)象也無(wú)法被釋放,因?yàn)樗鼈円脖患弦弥?/p>
private static HashMap<String, Object> a = new HashMap();
public static void main(String args[]) {
for (int i = 0; i < 1000; i++) {
Object tO = new Object();
a.put("0", tO);
tO = null;
}
}
如果僅僅釋放引用本身(tO = null),ArrayList依然在引用該對(duì)象,GC無(wú)法回收。
監(jiān)聽(tīng)器
在Java應(yīng)用中,通常會(huì)用到很多監(jiān)聽(tīng)器,一般通過(guò)addXXXXListener()實(shí)現(xiàn)。但釋放對(duì)象時(shí)通常會(huì)忘記刪除監(jiān)聽(tīng)器,從而增加內(nèi)存泄漏的風(fēng)險(xiǎn)。
各種連接
如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接(Socket)和I/O連接。忘記顯式調(diào)用close()方法引起的內(nèi)存泄漏。
內(nèi)部類和外部模塊的引用
內(nèi)部類的引用是很容易被遺忘的一種,一旦沒(méi)有釋放可能會(huì)導(dǎo)致一系列后續(xù)對(duì)象無(wú)法釋放。此外還要小心外部模塊不經(jīng)意的引用,內(nèi)部類是否提供相應(yīng)的操作去除外部引用。
單例模式
由于單例的靜態(tài)特性,使其生命周期與應(yīng)用的生命周期一樣長(zhǎng),一旦使用不恰當(dāng)極易造成內(nèi)存泄漏。如果單利持有外部引用,需要注意提供釋放方式,否則當(dāng)外部對(duì)象無(wú)法被正常回收時(shí),會(huì)進(jìn)而導(dǎo)致內(nèi)存泄漏。
常見(jiàn)的內(nèi)存泄漏處理方式:
集合類泄漏
如集合的使用范圍超過(guò)邏輯代碼的范圍,需要格外注意刪除機(jī)制是否完善可靠。比如由靜態(tài)屬性static指向的集合。
單利泄漏
以下為簡(jiǎn)單邏輯代碼,只為舉例說(shuō)明內(nèi)存泄漏問(wèn)題,不保證單利模式的可靠性。
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;
}
}
AppManager創(chuàng)建時(shí)需要傳入一個(gè)Context,這個(gè)Context的生命周期長(zhǎng)短至關(guān)重要。
1. 如果傳入的是Application的Context,因?yàn)锳pplication的生命周期等同于應(yīng)用的生命周期,所以沒(méi)有任何問(wèn)題。
2. 如果傳入的是Activity的Context,則需要考慮這個(gè)Activity是否在整個(gè)生命周期都不會(huì)被回收了,如果不是,則會(huì)造成內(nèi)存泄漏。
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
public class MyActivity extends AppCompatActivity {
private static MyInnerClass mInnerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (mInnerClass == null) {
mInnerClass = new MyInnerClass();
}
}
class MyInnerClass {
...
}
}
內(nèi)部類持有外部類引用,而static聲明的對(duì)象聲明周期通常會(huì)比Activity長(zhǎng)。即使關(guān)閉這個(gè)頁(yè)面,由于mInnerClass為靜態(tài)的,并且持有MyActivity的引用,導(dǎo)致無(wú)法回收此頁(yè)面從而引起內(nèi)存泄漏。
應(yīng)該將該內(nèi)部類單獨(dú)封裝為一個(gè)單例來(lái)使用。
匿名內(nèi)部類/異步線程
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
}
}
Runnable都使用了匿名內(nèi)部類,將持有MyActivity的引用。如果任務(wù)在Activity銷毀前未完成,將導(dǎo)致Activity的內(nèi)存無(wú)法被回收,從而造成內(nèi)存泄漏。
解決方法:將Runnable獨(dú)立出來(lái)或使用靜態(tài)內(nèi)部類,可以避免因持有外部對(duì)象導(dǎo)致的內(nèi)存泄漏。
Handler造成的內(nèi)存泄漏
public class SampleActivity extends AppCompatActivity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
...
}
}, 300000);
finish();
}
}
Handler屬于TLS(Thread Local Storage)變量,生命周期與Activity是不一致的,容易導(dǎo)致持有的對(duì)象無(wú)法正確被釋放
當(dāng)Android應(yīng)用程序啟動(dòng)時(shí),該應(yīng)用程序的主線程會(huì)自動(dòng)創(chuàng)建一個(gè)Looper對(duì)象和與之關(guān)聯(lián)的MessageQueue。
當(dāng)主線程中實(shí)例化一個(gè)Handler對(duì)象后,它就會(huì)自動(dòng)與主線程Looper的MessageQueue關(guān)聯(lián)起來(lái)。所有發(fā)送到MessageQueue的Messag都會(huì)持有Handler的引用,所以Looper會(huì)據(jù)此回調(diào)Handle的handleMessage()方法來(lái)處理消息。只要MessageQueue中有未處理的Message,Looper就會(huì)不斷的從中取出并交給Handler處理。
另外,主線程的Looper對(duì)象會(huì)伴隨該應(yīng)用程序的整個(gè)生命周期。
在Java中,非靜態(tài)內(nèi)部類和匿名類內(nèi)部類都會(huì)潛在持有它們所屬的外部類的引用,但是靜態(tài)內(nèi)部類卻不會(huì)。
當(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)。
解決方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無(wú)關(guān)了。同時(shí)通過(guò)弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進(jìn)去,見(jiàn)如下代碼:
public class SampleActivity extends AppCompatActivity {
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);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() { ... }
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mHandler.postDelayed(mRunnable, 300000);
finish();
}
}
避免不必要的靜態(tài)成員變量
對(duì)于BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等資源的使用,應(yīng)在Activity銷毀前及時(shí)關(guān)閉或注銷。
不使用WebView對(duì)象時(shí),應(yīng)調(diào)用`destroy()`方法銷毀。






