前言
基於MVP 模式的理解進行內存泄漏的測試
1.對MvpSample2工程的測試(測試記錄)
-
第一次: 清理弱引用和解除rx的訂閱,rx裏面沒開線程跑,雖然棧中存在presenter$tologin$1,但是引用爲0.,所以內存不泄漏
-
第二次:清理弱引用和解除rx的訂閱,rx裏面開了新的線程跑,presenter對象被線程所持有,activity引用不存在,presenter的內存泄漏了
-
第三次:清理弱引用和解除rx的訂閱,rx裏面上游開了循環(在上游的線程跑),雖然內存中存在presenter$tologin$1,內存不泄漏,跟第一次一樣
-
第四次:沒有清理弱引用和解除rx的訂閱,rx裏面沒開線程跑,雖然內存中存在presenter$tologin$1,但是引用爲0.,所以內存不泄漏
-
第五次:沒有清理弱引用和解除rx的訂閱,rx裏面上游開了循環(在上游的線程跑),presenter的引用被rx上游線程持有,釋放不了,內存泄漏了,而View已經不存在內存,是因爲View在P層中加了弱引用
-
第六次:V在P層中是強引用持有,沒有解除rx的訂閱,rx裏面上游開了循環(在上游的線程跑), presenter和View都回收不了,因爲線程持有P的引用,還在運行中
-
第七次:V在P層中是強引用持有,解除rx的訂閱,rx裏面上游開了循環(在上游的線程跑), 雖然內存中存在presenter$tologin$1,但是引用爲0.,所以內存不泄漏
2.截圖說明
-
截圖1 :從GC Roots節點到該對象(LoginPresenter)的最短引用路徑, 排除弱引用/軟引用
-
截圖2 : 從截圖1過渡到截圖2,可以看到LoginPresenter是被線程持有引用
-
截圖3 : 從profiler可以看到LoginPresenter是被線程持有引用
-
截圖4 : 從profiler可以看到LoginModel是被LoginPresenter持有引用,LoginPresenter是被線程持有引用
-
截圖5 : objects是存在實例的個數,Shallow Heap淺堆是Java對象佔用的內存,Retained Heap深堆是java對象及對象引用的類佔用的內存、jvm gc回收時釋放的內存
-
截圖6:V被P強引用持有引用,P被運行的任務(可能內部類)持有引用,導致V層和P層都內存泄漏
3.總結:
-
p層的耗時任務在頁面銷燬時是否執行很關鍵:假設當頁面銷燬時,presenter層內的任務執行完,由於presenter沒有再被內部類等持有引用,所以presenter是會被回收的,那view層也不被presenter持有引用,所以即使沒在View銷燬時清空軟引用和置View爲null,View同樣會被銷燬,不存在內存泄漏問題
-
V層是否被presenter弱引用持有決定V層是否會內存泄漏:假設當頁面銷燬時,presenter層內的任務在執行, 由於V是被presenter弱引用持有,所以V是會被GC回收的,而Presenter由於任務還在執行,所以回收不了
-
頁面銷燬時結束耗時任務可解決presenter和View的內存泄漏, 假設當頁面銷燬時,即使presenter對View是強引用持有,只要此時任務執行完或者解綁Rx的訂閱,presenter和View都是可以被回收的,所以不存在內存泄漏
-
Rx上游創建異步耗時線程跑,即使取消訂閱,還是會內存泄漏,可能Rx不知道開了一個子線程在跑,而子線程持有presenter的引用
-
線程的調度放心交給Rx來處理:Rx上游創建異步耗時線程跑,即使取消訂閱,還是會內存泄漏,可能Rx不知道開了一個子線程在跑,而子線程持有presenter的引用 (這裏參考鏈接 在Rx的上游執行異步耗時任務的測試)
-
所以發生泄漏主要在: presenter的引用被rx開闢的線程所持有(或者Model的引用被持有), 從而導致V的引用被持有
-
線程運行中,而View界面已經關閉,由於presenter不能被回收(被內部類持有引用),所以導致presenter內存泄漏
-
而如果View是被Presenter強引用持有的話,那View也不能被回收
-
而如果View是被presenter弱引用持有的話,那麼View是可以被GC回收的
-
-
View的界面銷燬, 此時線程運行結束或解除rx的訂閱,由於presenter已不再被持有引用,故可GC回收, 而不管View是被Presenter強引用還是弱引用,View都會被GC回收
-
-
MVP的內存泄漏可以通過解除Rx的訂閱(RxLifecycle2框架或AutoDispose框架)來解決,前提是耗時任務都在Rx裏去操作, 使得Model、View、Presenter不被持有引用,從而可回收