一、垃圾回收
一般來說刀疙,程序使用內存遵循先向操作系統(tǒng)申請一塊內存道宅,使用內存臼朗,使用完畢之后釋放內存歸還給操作系統(tǒng)。然而在傳統(tǒng)的C/C++等要求顯式釋放內存的編程語言中昏名,在合適的時候釋放內存是一個很有難度的工作涮雷,因此Java等編程語言都提供了基于垃圾回收算法的內存管理機制。
常見的垃圾回收算法有引用計數算法(Reference Counting)轻局、標注—清除算法(Mark and Sweep GC)洪鸭、復制算法(Copying GC)和逐代回收(Generational GC)等算法,其中Android虛擬機采用的是標注—清除算法仑扑,并不是大多數JVM實現(xiàn)里采用的逐代回收算法览爵。
二、幾個基本概念
內存泄露:程序在向系統(tǒng)申請分配內存空間后(new)镇饮,在使用完畢后未釋放蜓竹,結果導致不再使用的對象一直占據該內存單元,或者他們占用的內存沒有及時得到釋放储藐,從而造成內存空間不斷減少的現(xiàn)象俱济。由于Android程序可以使用的內存較少,發(fā)生內存泄漏會導致內存更加緊張邑茄,甚至最終由于內存耗盡而發(fā)生OOM(out of memeroy)錯誤姨蝴,導致應用崩潰。
內存溢出:程序向系統(tǒng)申請的內存空間超出了系統(tǒng)能給的肺缕。比如內存只能分配一個int類型左医,我卻要塞給他一個long類型,系統(tǒng)就出現(xiàn)oom同木。
強引用:強引用是使用最普遍的引用浮梢。如果一個對象具有強引用,那垃圾回收器絕不會回收它彤路。 當內存空間不足秕硝,Java虛擬機寧愿拋出OOM(OutOfMemoryError)錯誤,使程序異常終止洲尊,也不會靠隨意回收具有強引用的對象來解決內存不足的問題远豺。
軟引用:如果一個對象只具有軟引用,但內存空間足夠時坞嘀,垃圾回收器就不會回收它躯护;直到虛擬機報告內存不夠時才會回收, 只要垃圾回收器沒有回收它丽涩,該對象就可以被程序使用棺滞。軟引用可用來實現(xiàn)內存敏感的高速緩存裁蚁。 軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收继准,Java虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列中枉证。
弱引用:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區(qū)域的過程中移必,一旦發(fā)現(xiàn)了只具有弱引用的對象室谚,不管當前內存空間是否足夠,都會回收它的內存崔泵。 不過舞萄,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象管削。 弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用倒脓,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯(lián)的引用隊列中含思。
虛引用:虛引用可以理解為虛設的引用崎弃,與其他幾種引用都不同,虛引用并不會決定對象的生命周期含潘。如果一個對象僅持有虛引用饲做,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收遏弱。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動盆均。 虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。 當垃圾回收器準備回收一個對象時漱逸,如果發(fā)現(xiàn)它還有虛引用泪姨,就會在回收對象的內存之前,把這個虛引用加入到與之關聯(lián)的引用隊列中饰抒。 程序可以通過判斷引用隊列中是否已經加入了虛引用肮砾,來了解被引用的對象是否將要被垃圾回收。 如果程序發(fā)現(xiàn)某個虛引用已經被加入到引用隊列袋坑,那么就可以在所引用的對象的內存被回收之前采取必要的行動仗处。
三、Android內存泄漏存在的原因
1.靜態(tài)變量導致的內存泄漏
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static View mView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
mView = new View (this);
}
}
上面這段代碼就會產生內存泄漏枣宫,mView是靜態(tài)變量婆誓,它的內部持有當前的activity,因此Activity仍然無法釋放也颤。
2.單例模式導致的內存泄漏
不合理的單例模式也會引起內存泄漏洋幻,
public class LeakTest {
private static LeakTest mInstance;
private LeakTest(Context context) {
}
/**
* 單例模式
*/
public static LeakTest getInstance(Context context) {
if(mInstance == null) {
mInstance = new LeakTest (context);
}
return mInstance;
}
}
比如以上代碼是一個提供單例的測試類,需要傳入一個Contex進行獲取歇拆,在Activity中使用的時候如果傳入了改Activity的Context鞋屈,當Activity不再使用的時候,如果在onDestroy時不進行操作故觅,就會造成靜態(tài)的單例變量一直引用這個Context厂庇,導致本該釋放內存的變量一直占用內存造成內存泄漏。要解決這種類型的內存泄漏可以在引用Activity的Context時變成引用Application的Context输吏。
3.屬性動畫導致內存泄漏
屬性動畫中有一類無限循環(huán)的動畫权旷,如果在Activity中播放此類動畫但是沒有在onDestroy中去停止動畫,那么動畫就會一直播放贯溅,盡管在當前界面以及看不到動畫了拄氯。這個時候Activity的View會被動畫持有,View又持有這個Activity它浅,最終導致Activity無法釋放译柏,造成內存泄漏。解決方法是在onDestroy的時候調用animator.cancle()方法來停止動畫姐霍。
4.其他原因導致的內存泄漏
- 資源對象沒關閉造成的內存泄漏鄙麦,如查詢數據庫后沒有關閉游標cursor
- 構造Adapter時,沒有使用 convertView 重用
- Bitmap對象不再使用時镊折,沒有調用recycle()釋放內存
- 對象被生命周期長的對象引用胯府,如activity被靜態(tài)變量引用導致activity不能釋放,因為靜態(tài)類生命周期比Activity長恨胚。
- 想暫時規(guī)避內存泄漏問題,可以在manifest.xml加入:
android:largeHeap="true"
此時heapsize會增大2-3倍骂因,緩解OOM的發(fā)生
其他內存泄漏的原因參考:https://blog.csdn.net/cyq1028/article/details/19980369
四、內存泄漏的排查
1.LeakCanary
A memory leak detection library for Android and Java.
LeakCanary的工作機制:
RefWatcher.watch()
創(chuàng)建一個 KeyedWeakReference 到要被監(jiān)控的對象赃泡。然后在后臺線程檢查引用是否被清除寒波,如果沒有,調用GC升熊。
如果引用還是未被清除影所,把 heap 內存 dump 到 APP 對應的文件系統(tǒng)中的一個
.hprof
文件中。在另外一個進程中的
HeapAnalyzerService
有一個HeapAnalyzer
使用HAHA 解析這個文件僚碎。得益于唯一的 reference key,
HeapAnalyzer
找到KeyedWeakReference
猴娩,定位內存泄露。HeapAnalyzer
計算 到 GC roots 的最短強引用路徑勺阐,并確定是否是泄露卷中。如果是的話,建立導致泄露的引用鏈渊抽。引用鏈傳遞到 APP 進程中的
DisplayLeakService
蟆豫, 并以通知的形式展示出來。
LeakCanary的使用
1.添加依賴
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
2.新建一個Application類懒闷,用于使用LeakCanary
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
LeakCanary.install() 會返回一個預定義的 RefWatcher十减,同時也會啟用一個 ActivityRefWatcher栈幸,用于自動監(jiān)控調用 Activity.onDestroy() 之后泄露的 activity,LeakCanary自動監(jiān)控Activity,如果要在Fragment中使用LeakCanary需要在onDestroy方法中進行監(jiān)控帮辟。
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
只要繼承自基類的Fragment都會被監(jiān)控內存泄漏的情況速址。
3.LeakCanary的自定義和其他操作參考
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/