何為內存泄漏?
內存泄露(Memory leak),是指程序在向系統申請分配內存空間后(new),在使用完畢后未釋放。結果導致一直占據該內存單元,我們和程序都無法再使用該內存單元,直到程序結束,這是內存泄露。
JVM/ART
JVM(Java虛擬機)
是一個虛構出來的運行Java程序的運行時環境,是通過在實際的計算機上仿真模擬各種計算機功能的實現。它具有完善的硬件架構(如處理器、堆棧、寄存器等),還具有相應的指令系統,使用JVM就是使Java程序支持與操作系統無關。
理論上在任何操作系統中,只要有對應的JVM,即可運行Java程序。
ART(android虛擬機)
是在Android系統上運行Android程序的虛擬機,其指令集是基於寄存器架構的,執行特有的文件格式-dex字節碼來完成對象生命周期管理、堆棧管理、線程管理、安全異常管理、垃圾回收等重要功能。
其實ART就是在JVM基礎上專門為android移動設備定制的一套虛擬機方案。
內存區域分布
JAVA是在JVM所虛擬出的內存環境中運行的,JVM的內存可分為三個區:
堆(heap)、棧(stack)和方法區(method)。
棧(stack)
是簡單的數據結構,但在計算機中使用廣泛。棧最顯著的特征是:LIFO(Last In, First Out, 后進先出),棧中只存放基本類型和對象的引用(不是對象)
堆(heap)
堆內存用於存放由new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。JVM只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身。
方法區(method)
又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量
內存泄漏原因分析
那么問題來了?究竟哪部分的內存會導致內存泄漏呢?
在JAVA中JVM的棧記錄了方法的調用,每個線程擁有一個棧。
在線程的運行過程當中,執行到一個新的方法調用,就在棧中增加一個內存單元,即幀(frame)。在frame中,保存有該方法調用的參數、局部變量和返回地址。
然而JAVA中的局部變量只能是基本類型變量(int),或者對象的引用。所以在棧中只存放基本類型變量和對象的引用。引用的對象保存在堆中。
當某方法運行結束時,該方法對應的frame將會從棧中刪除,frame中所有局部變量和參數所占有的空間也隨之釋放。 線程回到原方法繼續執行,當所有的棧都清空的時候,程序也就隨之運行結束。
而對於堆內存,堆存放着普通變量。在JAVA中堆內存不會隨着方法的結束而清空,所以在方法中定義了局部變量,在方法結束后變量依然存活在堆中。
綜上所述,棧(stack)可以自行清除不用的內存空間。但是如果我們不停的創建新對象,堆(heap)的內存空間就會被消耗盡。所以內存泄漏會發生在堆區。
JAVA引入了垃圾回收(garbage collection,簡稱GC)去處理堆內存的回收。
垃圾回收機制
垃圾回收(garbage collection,簡稱GC)可以自動清空堆中不再使用的對象。
在JAVA中對象是通過引用使用的。如果再沒有引用指向該對象,那么該對象就無從處理或調用該對象,這樣的對象稱為不可到達(unreachable)。
垃圾回收用於釋放不可到達的對象所占據的內存。
根據上圖可以知道:由於obj4沒有root指向它,所以GC會釋放它所占據的內存,obj7由於還有其他引用指向它,所以得不到釋放(如果持有對象的引用,垃圾回收器是無法在內存中回收這個對象)
所以內存泄露的真因是:
持有對象的強引用,且沒有及時釋放,進而造成內存單元一直被占用,浪費空間,造成內存溢出
內存泄漏對應用的影響
內存泄漏對於app沒有直接危害,即使有發現內存泄漏的情況,也不一定會立即引起app崩潰,但是通過累積效應,應用會爆出各種問題:
1、內存得不到釋放,慢慢的會造成app內存溢出,導致崩潰
2、內存泄漏同時可能會觸發系統頻繁GC,發生內存抖動,會導致系統性能問題(卡頓不流暢)
測試場景選擇
- 新頁面打開
- 橫豎屏切換
- 滑動屏幕
測試方式
有源碼+Android Studio環境,借助Profiler
操作步驟:
- 打開App,進入到默認頁面(首頁),手動觸發GC,記錄此時的內存值
- 測試結束后,返回到默認頁面,手動觸發GC,同時記錄此時的內存值
- 兩者做比較,發現值存在較大差異,可以斷言發生了內存泄漏
- 此時可以點擊Dump Java Heap,收集此時的內存信息,完成之后會自動保存在后綴為hprof文件中
- 拿hprof文件做具體分析即可(可提交給開發)
無源碼,有debug版本的APK包,借助DDMS工具
DDMS是Android SDK中自帶的調試工具
需要注意的是:新版本的SDK中,DDMS工具已經集成到了Android device mointor中
操作步驟:
- 打開monitor.bat,鏈接設備
- 選擇要調試的進程,打開調試App->進入到首頁
- 點擊Update heap->Cause GC,記錄下此時data object這一欄數據
- 測試結束后,返回到默認頁面,點擊Cause GC,同時記錄data object這一欄數據
- 前后兩者做對比,發現值存在較大差異,可以斷言發生了內存泄漏
- 此時點擊Dump HPROF file按鈕,獲取保存有內存信息的hprof文件
- 拿hprof文件做具體分析即可(可提交給開發)
LeakCanary+Monkey(推薦)
LeakCanary是Square公司基於MAT開源的一個工具,用於檢測Android App的內存泄漏,我們可以通過集成LeakCanary提供的jar包到自己的項目工程中,一旦檢測到內存泄漏問題,LeakCanary會自動dump內存信息,通過另外一個進程分析內存泄漏信息並展示出來,可以隨時發現和定位內存泄漏問題。
在測試過程中,我們可以結合Monkey健壯性測試工具自動化執行,測試結束后,LeakCanary自動展示內存泄漏問題: