內(nèi)存泄漏
概念:
指程序在申請(qǐng)內(nèi)存后,當(dāng)該內(nèi)存不需再使用 但 卻無法被釋放 & 歸還給 程序的現(xiàn)象。即 程序申請(qǐng)的一塊內(nèi)存,且沒有任何一個(gè)指針指向它,那么這塊內(nèi)存就泄露了。
表現(xiàn):
它的一般表現(xiàn)方式是程序運(yùn)行時(shí)間越長(zhǎng),占用內(nèi)存越多,最終用盡全部?jī)?nèi)存,整個(gè)系統(tǒng)崩潰。
原因:
造成內(nèi)存泄漏的本質(zhì)原因是 持有引用者的生命周期 大于 被引用者的生命周期
影響:
內(nèi)存泄漏一般不會(huì)導(dǎo)致程序異常,但它會(huì)導(dǎo)致程序的內(nèi)存占用過大,這將提高內(nèi)存溢出的幾率。所以,內(nèi)存泄露是內(nèi)存溢出的一種原因之一,但不是唯一因素。
導(dǎo)致內(nèi)存泄漏的原因及解決方案:
1、集合類導(dǎo)致的內(nèi)存泄漏
原因:
集合類 添加元素后,仍引用著 集合元素對(duì)象,導(dǎo)致該集合元素對(duì)象不可被回收,從而 導(dǎo)致內(nèi)存泄漏
代碼演示:
private void memoryLeakOfCollection() {
// 通過 循環(huán)申請(qǐng)Object 對(duì)象 & 將申請(qǐng)的對(duì)象逐個(gè)放入到集合List
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 雖釋放了集合元素引用的本身:o=null)
// 但集合List 仍然引用該對(duì)象,故垃圾回收器GC 依然不可回收該對(duì)象
}
解決方案:
集合類 添加集合元素對(duì)象 后,在使用后必須從集合中刪除
objectList.clear();
objectList=null;
2、靜態(tài)變量導(dǎo)致的內(nèi)存泄漏
原因:
靜態(tài)變量是在類被load的時(shí)候分配內(nèi)存的,并且存在于方法區(qū)。當(dāng)類被卸載的時(shí)候,靜態(tài)變量被銷毀。只要靜態(tài)變量沒有被銷毀也沒有置null,其對(duì)象一直被保持引用,也即引用計(jì)數(shù)不可能是0,因此不會(huì)被垃圾回收。
所以 Static 關(guān)鍵字修飾的成員變量的生命周期 = 應(yīng)用程序的生命周期。
代碼演示:
public class YouhuaTestActivity extends AppCompatActivity {
private static Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_youhua_test);
// Activity無法正常銷毀,因?yàn)殪o態(tài)變量mContext引用了它。
mContext = this;
}
}
解決方案:
A、盡量避免 Static 成員變量引用資源耗費(fèi)過多的實(shí)例(如 Context),若需引用 Context,則盡量使用Applicaiton的Context
B、使用 弱引用(WeakReference) 代替 強(qiáng)引用 持有實(shí)例
靜態(tài)變量引起內(nèi)存泄漏的一個(gè)特殊例子--單例模式:
原因:
當(dāng)單列中傳入一個(gè)Activity的Context后,該單列就持有Activity的引用,我們知道單例的生命周期和Application的一樣長(zhǎng),所以當(dāng)Activity退出時(shí)它的內(nèi)存并不會(huì)被回收。
代碼演示:
private AppManager(Context context) {
// 創(chuàng)建單例時(shí),需傳入一個(gè)Context
// 若傳入的是Activity的Context,此時(shí)單例 則持有該Activity的引用
// 由于單例一直持有該Activity的引用(直到整個(gè)應(yīng)用生命周期結(jié)束),即使該Activity退出,該Activity的內(nèi)存也不會(huì)被回收
// 特別是一些龐大的Activity,此處非常容易導(dǎo)致OOM
this.context = context;
}
解決方案:
論傳入什么類型的Context,最終單例使用的都是Application的Context
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
3、非靜態(tài)內(nèi)部/匿名類
非靜態(tài)內(nèi)部類默認(rèn)持有外部類的引用;而靜態(tài)內(nèi)部類則不會(huì)。
3.1、非靜態(tài)內(nèi)部類的實(shí)例是靜態(tài)時(shí) 引起當(dāng)內(nèi)存泄漏
原因:
當(dāng)非靜態(tài)內(nèi)部類當(dāng)實(shí)例是靜態(tài)時(shí),那么這個(gè)實(shí)例當(dāng)生命周期就和程序當(dāng)生命周期一樣長(zhǎng),而非靜態(tài)內(nèi)部類持有外部類都引用,導(dǎo)致外部類無法被釋放,最終造成內(nèi)存泄漏。
代碼演示:
public class TestActivity extends AppCompatActivity {
// 非靜態(tài)內(nèi)部類的實(shí)例的引用是靜態(tài)當(dāng)
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保證非靜態(tài)內(nèi)部類的實(shí)例只有1個(gè)
if (innerClass == null)
innerClass = new InnerClass();
}
// 定義非靜態(tài)內(nèi)部類
private class InnerClass {
}
}
解決方案:
1、盡量避免非靜態(tài)內(nèi)部類所創(chuàng)建的實(shí)例 = 靜態(tài);將非靜態(tài)內(nèi)部類設(shè)置為靜態(tài)內(nèi)部類(因?yàn)殪o態(tài)內(nèi)部類默認(rèn)不持有外部累當(dāng)引用)。
2、將內(nèi)部類抽取出來封裝成一個(gè)單例
3.2、多線程:AsyncTask、實(shí)現(xiàn)Runnable接口、繼承Thread類 引起的內(nèi)存泄漏
泄漏原因:
當(dāng)工作線程正在處理任務(wù)且外部類需銷毀時(shí),由于工作線程實(shí)例持有外部類引用,將使得外部類無法被垃圾回收器(GC)回收,從而造成 內(nèi)存泄露。
代碼:
public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 異步任務(wù)和Runnable都是一個(gè)匿名內(nèi)部類,因此它們對(duì)當(dāng)前Activity都有一個(gè)隱式引用。
// 如果Activity在銷毀之前,任務(wù)還未完成,那么將導(dǎo)致Activity的內(nèi)存資源無法回收,造成內(nèi)存泄漏
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
// 通過創(chuàng)建的內(nèi)部類 實(shí)現(xiàn)多線程
new MyThread().start();
}
// 自定義的Thread子類
private class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
優(yōu)化方案一:使用靜態(tài)內(nèi)部類
public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
}
private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
}
優(yōu)化方案二:強(qiáng)制結(jié)束線程
當(dāng) 外部類(此處以Activity為例) 結(jié)束生命周期時(shí)(此時(shí)系統(tǒng)會(huì)調(diào)用onDestroy()),強(qiáng)制結(jié)束線程(調(diào)用stop()),使得工作線程實(shí)例的生命周期與外部類的生命周期 同步
@Override
protected void onDestroy() {
super.onDestroy();
Thread.stop();
// 外部類Activity生命周期結(jié)束時(shí),強(qiáng)制結(jié)束線程
}
3.3、Handler造成的內(nèi)存泄漏
原因:
大家平時(shí)開發(fā)中喜歡在Activity中直接new一個(gè)Handler的匿名內(nèi)部類,在Java中,非靜態(tài)的內(nèi)部類或者匿名類會(huì)隱式的持有其外部類的引用,而靜態(tài)的內(nèi)部類則不會(huì)。這樣造成匿名內(nèi)部類持有一個(gè)外部類(通常是Activity)的引用(不然怎么更新ui),但是Handler常常伴隨著一個(gè)執(zhí)行耗時(shí)操作的異步線程(如下載多張圖片),如果在完成耗時(shí)操作之前,Activity退出,異步線程持有handler的引用,handler持有Activity的引用,從而導(dǎo)致內(nèi)存泄漏。
代碼:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// do something
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...do request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
優(yōu)化方案:
public class MainActivity extends AppCompatActivity {
/**
* 創(chuàng)建一個(gè)靜態(tài)Handler內(nèi)部類,然后對(duì)Handler持有的對(duì)象使用弱引用,這樣在回收時(shí)也可以回收Handler持有的對(duì)象,
* 這樣雖然避免了Activity泄漏,不過Looper線程的消息隊(duì)列中還是可能會(huì)有待處理的消息,
* 所以我們?cè)贏ctivity的Destroy時(shí)或者Stop時(shí)應(yīng)該移除消息隊(duì)列中的消息,
*/
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 移除消息隊(duì)列中所有消息和所有的Runnable,
mHandler.removeCallbacksAndMessages(null);
// 當(dāng)然,也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。
}
}
4、資源對(duì)象未關(guān)閉造成當(dāng)內(nèi)存泄漏
原因:
對(duì)于資源的使用(如 廣播BraodcastReceiver、文件流File、數(shù)據(jù)庫游標(biāo)Cursor、圖片資源Bitmap等),
若在Activity銷毀時(shí)無及時(shí)關(guān)閉 / 注銷這些資源,這些資源將不會(huì)被回收,從而造成內(nèi)存泄漏。
優(yōu)化方案:
在Activity銷毀時(shí) 及時(shí)關(guān)閉 / 注銷資源
如下:
4.1、BraodcastReceiver 及時(shí)注銷:unregisterReceiver()
4.2、文件流File:及時(shí)關(guān)閉流:InputStream / OutputStream.close()
4.3、數(shù)據(jù)庫游標(biāo)cursor使用后關(guān)閉游標(biāo):cursor.close()
4.4、圖片資源Bitmap:Android分配給圖片的內(nèi)存只有8M,若1個(gè)Bitmap對(duì)象占內(nèi)存較多,當(dāng)它不再被使用時(shí),應(yīng)調(diào)用recycle()回收此對(duì)象的像素所占用的內(nèi)存;最后再賦為null
4.5、自定義View時(shí)TypedArray使用完后忘記調(diào)用recycle()方法釋放內(nèi)存
Bitmap.recycle();
Bitmap = null;
4.5、屬性動(dòng)畫中有一類無限循環(huán)的動(dòng)畫,如果在Activity中播放此動(dòng)畫且沒有在onDestory中停止動(dòng)畫,即使已經(jīng)無法在界面上看到動(dòng)畫效果,動(dòng)畫也會(huì)一直播放下去,并且這個(gè)時(shí)候Activity的View會(huì)被動(dòng)畫持有,而View有持有了Activity,最終導(dǎo)致Activity無法釋放。這種泄漏的解決辦法是在onDestory中調(diào)用animator.cancal()來停止動(dòng)畫。
5、上下文對(duì)象導(dǎo)致的內(nèi)存泄漏
5.1、使用application的context來替代activity相關(guān)的context。
盡量避免activity的context在自己的范圍外被使用,這樣會(huì)導(dǎo)致activity無法釋放。不要讓生命周期長(zhǎng)于Activity的對(duì)象持有到Activity的引用
5.2、在Android中,Application Context的生命周期和應(yīng)用的生命周期一樣長(zhǎng),而不是取決于某個(gè)Activity的生命周期。
如果想保持一個(gè)長(zhǎng)期生命的對(duì)象,并且這個(gè)對(duì)象需要一個(gè) Context,就可以使用Application對(duì)象??梢酝ㄟ^調(diào)用Context.getApplicationContext()方法或者 Activity.getApplication()方法來獲得Application對(duì)象。
5.3、Drawable的對(duì)象的內(nèi)部Callback持有activity的引用,當(dāng)Activity finish()之后,靜態(tài)成員drawable始終持有這個(gè)Activity的引用,導(dǎo)致內(nèi)存釋放不了。
5.4、Activity內(nèi)部如果有一個(gè)Context的成員變量,將導(dǎo)致Context引用指向的Activity對(duì)象釋放不了,見上文:靜態(tài)變量導(dǎo)致的內(nèi)存泄漏
6、webview導(dǎo)致當(dāng)內(nèi)存泄漏
http://lipeng1667.github.io/2016/08/06/memory-optimisation-for-webview-in-android/
7、其他
未采用軟引用等
- 軟引用:
如果一個(gè)對(duì)象只具有軟引用,那么如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存 空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒有回收它,該對(duì)象就可以被程序使用。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。軟引用可以和一個(gè)引用隊(duì) 列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。 - 弱引用:
如果一個(gè)對(duì)象只具有弱引用,那么在垃圾回收器線程掃描的過程中,一旦發(fā)現(xiàn)了只具有弱引 用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。弱 引用也可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián) 的引用隊(duì)列中。 - 弱引用與軟引用的根本區(qū)別在于:
只具有弱引用的對(duì)象擁有更短暫的生命周期,可能隨時(shí)被回收。而只具有軟引用的對(duì)象只有當(dāng)內(nèi)存不夠的時(shí)候才被回收,在內(nèi)存足夠的時(shí)候,通常不被回收。