Android內存泄漏是一個常常要遇到的問題,程序在內存泄漏的時候很容易致使OOM的發生。那麼如何查找內存泄漏和避免內存泄漏就是須要知曉的一個問題,首先咱們須要知道一些基礎知識。java
強引用: 強引用是Java中最普通的引用,隨意建立一個對象而後在其餘的地方引用一下,就是強引用,強引用的對象Java寧願OOM也不會回收他android
軟引用: 軟引用是比強引用弱的引用,在Java gc的時候,若是軟引用所引用的對象被回收,首次gc失敗的話會繼而回收軟引用的對象,軟引用適合作緩存處理 能夠和引用隊列(ReferenceQueue)一塊兒使用,當對象被回收以後保存他的軟引用會放入引用隊列算法
弱引用: 弱引用是比軟引用更加弱的引用,當Java執行gc的時候,若是弱引用所引用的對象被回收,不管他有沒有用都會回收掉弱引用的對象,不過gc是一個比較低優先級的線程,不會那麼及時的回收掉你的對象。 能夠和引用隊列一塊兒使用,當對象被回收以後保存他的弱引用會放入引用隊列緩存
虛引用: 虛引用和沒有引用是同樣的,他必須和引用隊列一塊兒使用,當Java回收一個對象的時候,若是發現他有虛引用,會在回收對象以前將他的虛引用加入到與之關聯的引用隊列中。 能夠經過這個特性在一個對象被回收以前採起措施併發
下面是一個例子:oracle
public class Main { public static void main(String[] args) throws InterruptedException { ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); String sw = "虛引用"; switch (sw) { case "軟引用": Object objSoft = new Object(); SoftReference<Object> softReference = new SoftReference<>(objSoft, referenceQueue); System.out.println("GC前獲取:" + softReference.get()); objSoft = null; System.gc(); Thread.sleep(1000); System.out.println("GC後獲取:" + softReference.get()); System.out.println("隊列中的結果:" + referenceQueue.poll()); break; /* * GC前獲取:java.lang.Object@61bbe9ba * GC後獲取:java.lang.Object@61bbe9ba * 隊列中的結果:null * */ case "弱引用": Object objWeak = new Object(); WeakReference<Object> weakReference = new WeakReference<>(objWeak, referenceQueue); System.out.println("GC前獲取:" + weakReference.get()); objWeak = null; System.gc(); Thread.sleep(1000); System.out.println("GC後獲取:" + weakReference.get()); System.out.println("隊列中的結果:" + referenceQueue.poll()); /* * GC前獲取:java.lang.Object@61bbe9ba * GC後獲取:null * 隊列中的結果:java.lang.ref.WeakReference@610455d6 * */ break; case "虛引用": Object objPhan = new Object(); PhantomReference<Object> phantomReference = new PhantomReference<>(objPhan, referenceQueue); System.out.println("GC前獲取:" + phantomReference.get()); objPhan = null; System.gc(); //此處的區別是當objPhan的內存被gc回收以前虛引用就會被加入到ReferenceQueue隊列中,其餘的引用都爲當引用被gc掉時候,引用會加入到ReferenceQueue中 Thread.sleep(1000); System.out.println("GC後獲取:" + phantomReference.get()); System.out.println("隊列中的結果:" + referenceQueue.poll()); /* * GC前獲取:java.lang.Object@61bbe9ba * GC後獲取:null * 隊列中的結果:java.lang.ref.WeakReference@610455d6 * */ break; } } }
目前oracle jdk和open jdk的虛擬機都爲Hotspot,android 爲Dalvik和Artide
曾經的GC算法:引用計數工具
簡短的說引用計數就是對每個對象的引用計算數字,若是引用就+1,不引用就-1,回收掉引用計數爲0的對象。來達到垃圾回收post
弊端:若是兩個對象都應該被回收可是他倆卻互相依賴,那麼他二者的引用永遠都不會爲0,那麼就永遠沒法回收, 沒法解決循環引用的問題ui
這個算法只在不多數的虛擬機中使用過
現代的GC算法
以上四種算法信息引用自QQ空間團隊分享 Android GC 那點事 &version=11000003&pass_ticket=nhSGhYD4LC9FWvUPv26Y7AdIzqEDu8FTImf2AKlyrCk%3D) ,總結的特別棒
對象在GC Root中可達,也就是他的引用不爲空,因此GC沒法回收它也就會致使內存泄漏
GC Root起點
當一個對象在引用鏈中失S#x53BB;了引用,那麼他就真的要告別世界了嗎,其實並非,虛擬機會給他「緩刑」,每個對象有一個finalize() 方法,虛擬機是否給他緩刑取決於這個對象的這個方法是否被執行,若是這個對象的這個方法沒有被覆蓋或者這個方法被執行過一次,那麼就要「行刑」了。真的是「續一秒」
若是這個對象的finalize()方法應該被執行,那麼虛擬機會將它放在F-Queue隊列中,稍後虛擬機會自動建立一個Finalizer線程去執行這個隊列中的對象的這個方法。若是對象在finalize()中成功自救,舉個例子,把本身和一個存在的對象強引用,那麼就不會被回收,不然就真的被回收了。
可是虛擬機並不會保證Finalizer線程執行結束再進行回收,由於若是在某一個對象的finalize()方法中執行了死循環或者超級耗時的操做,虛擬機等待這個執行結束的話就會致使整個Gc崩潰了
首先注意這個方法只能被執行一次,第二次就會標記了這個方法被執行過不會再執行了,其次,這個方法不必定會被執行到,因此不要依賴finalize()去自救。這不是好的作法。
Android2.3以後支持了併發的GC。
二者的差異:
首先非併發GC簡單粗暴,直接掛起全部的線程,此時Java堆中確定不會有任何的添加和修改,此時去遞歸GC樹,而後標記-清理。可是這樣會形成很大的開銷,你們都等着你豈不是很沒面子= =
然而非併發的GC是一點一點來的,跟線程同步進行這樣就不會有很長時間的等待,可是你要明白一個道理,想把地掃乾淨這段時間必須沒人來踩,因此他要有掛起線程的過程。
那麼併發是怎麼實現的呢?首先有個知識點就是Jvm在分配內存的時候,有兩種方式
建立對象是一個頻繁的操做,那麼咱們如何保證原子性呢?兩種方案
咱們用的是第二種 233
因此獲取Java堆鎖的時候,重點來了,咱們逐個線程去鎖TLAB,而不是一次全鎖住,固然提升了併發GC的效率,因此更快。可是引來的問題就是併發的問題,因此下一步要重複去修改在一個個探索時候被改的對象。也就須要更多的CPU資源。
首先咱們知道虛擬機如何去GC才能瞭解到如何讓一個對象被正確的回收,這樣纔不能內存泄漏
其次不管是併發GC仍是非併發GC都會致使掛起其餘的全部線程,那麼就會帶來程序卡頓。
ART在GC上作到了更加細粒度的控制,能夠更加流暢的GC
首先鋪墊一句話:非靜態的內部類和匿名類會隱式的持有外部類的引用
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { Log.d("smallSohoSolo", "Hello Handler"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler.postDelayed(new Runnable() { @Override public void run() { Log.d("smallSohoSolo", "Running"); } }, 1000 * 60 * 10); //10分鐘以後執行 finish(); } }
這段代碼有很明顯的內存泄漏,首先Handler和Runnable都是匿名內部類的實例,他們都會持有MainActivity的引用,
有人可能會說短暫的內存泄漏又能怎樣?這是錯誤的想法,由於只要發生內存泄漏,在這段時間只要進行了大內存的操做(好比加載一個照片牆),就有風險由於這個內存泄漏形成OOM(佔用內存確定剩下的少了)
上面這個如何修改呢?
將Runnable和Handler改爲static 或者在外部定義內部使用。
簡單粗暴 —> LeakCanary: Square出品的庫,當出現內存泄漏的時候會出現
精打細算 —> Android Studio 內存工具: 能夠Dump下來當前的內存路徑,而後分析出來哪些對象目前的狀態。很強