一、內(nèi)存泄露和內(nèi)存溢出
內(nèi)存泄露(Memory Leak):是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間涡戳;
內(nèi)存溢出(Out Of Memory):指程序在申請內(nèi)存時层坠,沒有足夠的內(nèi)存空間供其使用;即應用程序所需內(nèi)存 超出 系統(tǒng)為其分配的內(nèi)存限額抡锈。
二者關(guān)系:內(nèi)存溢出的根本原因就是內(nèi)存泄漏,ML會導致OOM。
二岛琼、內(nèi)存泄露的影響
內(nèi)存泄漏是造成應用程序OOM的主要原因之一,由于Android系統(tǒng)為每個應用程序分配的內(nèi)存有限纪蜒,當一個應用中產(chǎn)生的內(nèi)存泄漏比較多時衷恭,就會導致應用所需要的內(nèi)存超系統(tǒng)為其分配的內(nèi)存限額,這就造成了內(nèi)存溢出纯续,從而導致應用Crash随珠。
特殊現(xiàn)象:有時App發(fā)生Crash退出后又會重新啟動的現(xiàn)象。原因是App雖然退出猬错,但某個位置可能還持有歡迎頁(啟動頁)SplashActivity的引用窗看。
三、內(nèi)存泄露的原因
1倦炒、Java內(nèi)存分配策略
靜態(tài)存儲區(qū):又稱方法區(qū)显沈,主要存儲全局變量和靜態(tài)變量,在整個程序運行期間都存在逢唤;
堆區(qū):保存動態(tài)產(chǎn)生的數(shù)據(jù)拉讯,如:new出來的對象和數(shù)組,在不使用的時候由JVM GC回收鳖藕;
棧區(qū):方法體的局部變量會在棧區(qū)創(chuàng)建空間魔慷,并在方法執(zhí)行結(jié)束后會被JVM自動釋放變量的空間和內(nèi)存;
2著恩、本質(zhì)原因
本該被回收的對象因為某些原因而不能被回收院尔,從而繼續(xù)停留在堆內(nèi)存中。當一個對象本該被GC回收時喉誊,但有另一個正在使用的對象持有它的引用邀摆,從而導致它不能被GC回收而停留在堆內(nèi)存中。即長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄漏.
四伍茄、Android內(nèi)存泄露主要原因
(一)static關(guān)鍵字修飾的成員變量
1栋盹、static修飾context造成的內(nèi)存泄漏
問題描述:static是Java中的一個關(guān)鍵字,當用它來修飾成員變量時敷矫,那么該變量就屬于該類例获,而不是該類的實例音念。用static這個關(guān)鍵字修飾變量,使得變量的生命周期大大延長躏敢,并且訪問的時候闷愤,也極其的方便,用類名就能直接訪問件余,各個資源間傳值也極其的方便讥脐,所以它經(jīng)常被使用。但如果用它來引用一些資源耗費過的實例(Context的情況最多)啼器,這時就要謹慎對待了旬渠。
public class ClassName {
private static Context mContext;
}
以上代碼很容易出現(xiàn)內(nèi)存泄露,因為如果mContext的賦值是Activity的端壳,那么即使Activity已經(jīng)finish 后告丢,執(zhí)行onDestory函數(shù),但是由于它的對象還是被其它的類引用损谦,導致Activity依然不會被釋放岖免。如果該Activity里面再持有一些資源,同樣那些資源也沒有被釋放照捡,這個時候就會導致內(nèi)存泄露颅湘;
解決辦法:
?(1).盡量避免 Static 成員變量引用資源耗費過多的實例(如Context)。
(2).使用弱引用WeakReference代替強引用持有實例栗精。比如可以使用WeakReference mContextRef闯参。
2、單例造成的內(nèi)存泄露
問題描述:Android的單例模式使用的不恰當會造成內(nèi)存泄漏悲立。因為單例的靜態(tài)特性使得單例的生命周期和應用的生命周期一樣長鹿寨,如果一個對象已經(jīng)不需要使用了,而單例對象還持有該對象的引用薪夕,那么這個對象將不能被正辰挪荩回收,這就導致了內(nèi)存泄漏寥殖。
public class ClassB {
private static ClassB instance;
private Context context;
private ClassB(Context context) {this.context = context;}
public static ClassB getInstance(Context context) {
if (instance == null) ?{ ??instance = new ClassB(context); ? ?} ? ??return instance; ?}
}
傳入的是Application的Context:單例的生命周期和Application的一樣長玩讳,這將沒有任何問題涩蜘;
傳入的是Activity的Context:由于該Context和Activity的生命周期一樣長(Activity間接繼承于Context)嚼贡,單例的生命周期可能大于Activity的生命周期。當這個Context所對應的Activity退出時它的內(nèi)存并不會被回收同诫,因為單例對象持有該Activity的引用粤策。
解決辦法:
Context 盡量使用Application Context,因為Application的Context的生命周期比較長误窖,引用它不會出現(xiàn)內(nèi)存泄露的問題叮盘。正確的單例應該為下面的方式:
this.context =?context.getApplicationContext();
(二)非靜態(tài)內(nèi)部類/匿名類
1秩贰、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
問題描述:在啟動頻繁的Activity中,為了避免重復創(chuàng)建相同的數(shù)據(jù)資源柔吼,會在Activity內(nèi)部創(chuàng)建一個非靜態(tài)內(nèi)部類的單例毒费,每次啟動Activity時都會使用該單例的數(shù)據(jù)。若非靜態(tài)內(nèi)部類所創(chuàng)建的實例的生命周期等于應用的生命周期愈魏,會因非靜態(tài)內(nèi)部類持有外部類的引用觅玻,而導致外部類無法釋放,最終造成內(nèi)存泄露培漏。
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
?????mManager = new TestResource();
}
}
private class TestResource {
????//...
}
}
以上代碼就在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例溪厘,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復創(chuàng)建牌柄,卻會造成內(nèi)存泄漏畸悬。非靜態(tài)內(nèi)部類會持有外部類的引用,而又使用非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例珊佣,該實例和應用的生命周期一樣長蹋宦,這就導致該靜態(tài)實例一直會持有Activity的引用,導致Activity的內(nèi)存資源不能回收咒锻。
解決辦法:
1.將非靜態(tài)內(nèi)部類改為靜態(tài)內(nèi)部類(靜態(tài)內(nèi)部類默認不持有外部類的引用)2.該內(nèi)部類抽取出來封裝成一個單例妆档。若需使用Context,建議使用 Application 的 Context
2虫碉、Handler造成的內(nèi)存泄漏
問題描述:Handler的使用造成的內(nèi)存泄漏問題應該最為常見贾惦,在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請求回調(diào)等api都應該會借助Handler來處理,對于Handler的使用代碼編寫一不規(guī)范即有可能造成內(nèi)存泄漏敦捧,如下示例:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...doSomething
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request http
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
這種創(chuàng)建Handler的方式會造成內(nèi)存泄漏须板,由于mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例,所以它持有外部類Activity的引用兢卵,消息隊列MessageQueue在一個Looper線程中不斷輪詢處理消息习瑰,那么當這個Activity退出時,消息隊列中還有未處理的消息Message或者正在處理消息秽荤,而消息隊列中的Message持有mHandler實例的引用甜奄,mHandler又持有Activity的引用,所以導致該Activity的內(nèi)存資源無法及時回收窃款,引發(fā)內(nèi)存泄漏课兄。
解決辦法:
1.創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用晨继,這樣在回收時也可以回收Handler持有的對象烟阐,這樣雖然避免了Activity泄漏。2.Looper線程的消息隊列中還是可能會有待處理的消息,所以在Activity的Destroy時或者Stop時應該移除消息隊列中的消息蜒茄。
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler;
private TextView mTextView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
mHandler = new MyHandler(this);
loadData();
}
private void loadData() {
//...request http
Message message = Message.obtain();
mHandler.sendMessage(message);
}
private static class MyHandler extends Handler {
private WeakReference 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 onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
3唉擂、多線程造成的內(nèi)存泄漏(AsyncTask、實現(xiàn)Runnable接口檀葛、繼承Thread類)
問題描述:工作線程Thread類屬于非靜態(tài)內(nèi)部類/匿名內(nèi)部類玩祟,運行時默認持有外部類的引用。當工作線程運行時屿聋,若外部類MainActivity需銷毀卵凑,由于此時工作線程類實例持有外部類的引用,將使得外部類無法被垃圾回收器(GC)回收胜臊,從而造成內(nèi)存泄露勺卢。
對于線程造成的內(nèi)存泄漏,也是平時比較常見的象对,如下這兩個示例:
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
解決辦法:
靜態(tài)內(nèi)部類--靜態(tài)內(nèi)部類不持有外部類的引用黑忱,從而使得工作線程實例不會持有外部類引用。
當外部類結(jié)束生命周期時勒魔,強制結(jié)束線程--使得工作線程實例的生命周期與外部類的生命周期同步甫煞。使用靜態(tài)內(nèi)部類 & 強制結(jié)束線程 的方式,如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new MyRunnable()).start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Thread.stop();// 外部類Activity生命周期結(jié)束時冠绢,強制結(jié)束線程
}
}
(三)資源未關(guān)閉造成的內(nèi)存泄漏
問題描述:對于資源的使用(如 廣播BraodcastReceiver抚吠、文件流File、數(shù)據(jù)庫游標Cursor弟胀、圖片資源Bitmap等)楷力,若在Activity銷毀時無及時關(guān)閉或者注銷這些資源,則這些資源將不會被回收孵户,從而造成內(nèi)存泄漏萧朝。
解決辦法:
在Activity銷毀時及時關(guān)閉或者注銷資源
//廣播BraodcastReceiver:注銷注冊
unregisterReceiver()
//文件流File:關(guān)閉流
InputStream/OutputStream.close()
//數(shù)據(jù)庫游標cursor:使用后關(guān)閉游標
cursor.close()
//圖片資源Bitmap:當它不再被使用時,應調(diào)用recycle()回收此對象的像素所占用的內(nèi)存夏哭;最后再賦為null
Bitmap.recycle()
Bitmap = null
五检柬、總結(jié)
以上是Android內(nèi)存泄漏的三大主要原因:
1、static關(guān)鍵字引起的內(nèi)存泄漏
2竖配、非靜態(tài)內(nèi)部類/匿名類引起的內(nèi)存泄漏
3何址、資源未關(guān)閉造成的內(nèi)存泄漏