前言
在日常開發(fā)中,可能經(jīng)常會遇到一些莫名奇妙的崩潰問題,但是仔細查看代碼邏輯卻似乎也找不出代碼中有哪些不對的邏輯。這時候就需要仔細分析,你的代碼中是否存在內(nèi)存泄漏的問題。LeakCanary是Square公司開源的一款性能優(yōu)化工具,它能夠幫你方便的分析你的app中是否存在內(nèi)存泄漏的問題。在使用LeakCanary之前,讓我們先來了解幾個概念。
一些概念
Java虛擬機
相信學(xué)過Android的你對Java虛擬機一定不會陌生,但還是簡單的介紹一下Java虛擬機。如果你學(xué)過C/C++,一些C/C++書中都會強調(diào)你需要手動分配內(nèi)存,并在使用之后手動回收內(nèi)存。但是好像Java中從來沒有人讓你釋放內(nèi)存啊,這一切都需要歸功于Java虛擬機,Java虛擬機能夠幫我們自動釋放可回收內(nèi)存。當然,java虛擬機除了能夠幫助我們自動回收內(nèi)存之外,還有很多別的功能,但本文的重點在于內(nèi)存泄漏,而安卓使用的虛擬機與Java虛擬機相似。所以下面我們聊一聊Java虛擬機的垃圾回收吧。-
GC
GC全稱(Garbage Collection),也就是垃圾回收,Java虛擬機主要通過兩種途徑自動幫我們回收內(nèi)存。-
引用計數(shù)
Java虛擬機會給對象增加一個引用計數(shù)器,每當程序引用一次對象,計數(shù)器就會加一;反之,每當一個引用計數(shù)器失效時,計數(shù)器就會減一。當計數(shù)器的值為0,則說明此對象沒有被引用,可以被回收。
舉個例子Object obj = new Object(); // 計數(shù)器 + 1 = 1 obj = null; // 計數(shù)器 - 1 = 0,GC回收 // 但是如果對象相互調(diào)用,引用計數(shù)器就無法使得GC回收 Object a = new Object(); // a的引用計數(shù)為1 Object b = new Object(); // b的引用計數(shù)為1 a.next = b; // a的引用計數(shù)為2 b.next = a; // b的引用計數(shù)為2 a = null; // a的引用計數(shù)為1,盡管已經(jīng)顯示地將a賦值為null,但是由于引用計數(shù)為1,GC無法回收a b = null; // b的引用計數(shù)為1,同理,GC也不回收b
為了解決對象之間相互引用導(dǎo)致的無法GC的問題,Java虛擬機還有另一種GC策略。
-
可達性分析
設(shè)立若干根對象(GC Root) ,每個對象都是一個子節(jié)點,當一個對象找不到根節(jié)點,也就是無人引用時,標志其不可達。
可以作為GC Root的對象包括:
1.jvm棧中引用的對象
2.方法區(qū)中靜態(tài)變量引用的對象
3.方法區(qū)中常量引用的對象
4.本地方法棧中引用的對象
5.新生代,活不了多久就死的對象,比如局部變量,用復(fù)制算法[1]
6.老年代,生命周期長的對象,活的久不過也是會死的,用標記清除算法[2]
7.永久代[3] -----基本上GC不回收
但即使Java虛擬機已經(jīng)如此優(yōu)秀,它也不能保證所有的可回收內(nèi)存都能正常回收,
-
引用計數(shù)
-
內(nèi)存泄漏
內(nèi)存泄漏是指在app運行的過程中,由于內(nèi)存并沒有合理的回收,如:生命周期長的對象持有了生命周期短的對象的引用,導(dǎo)致生命周期短的對象一直無法回收。當這種情況累積到一定程度,可分配的棧內(nèi)存不足的時候,就會導(dǎo)致OOM,我們就看到了程序崩潰。而且這種崩潰不像一般的程序崩潰那樣能夠復(fù)現(xiàn),所以直接由程序崩潰,導(dǎo)致了程序員崩潰。
例如- 在onDestroy()調(diào)用Android活動實例的方法后,不再需要該活動實例,并且在靜態(tài)字段中存儲對該活動的引用將防止其被垃圾回收。
- 添加一個Fragment到backstack而沒有在Fragment.onDestroyView()中清除它的view的成員。
- 在一個對象中以成員的方式保存了一個Activity的context,而Activity在配置更改時依然存在。
- 注冊的綁定生命周期對象的監(jiān)聽、廣播接收器、RxJava訂閱等,在生命周期結(jié)束的時候忘記取消注冊。
OOM
OOM是(Out Of Memory)的簡稱,就是內(nèi)存不足的意思,類似的問題也有StackOverflow,寫一個簡單的沒有出口的遞歸函數(shù)就能看到。
使用LeakCanary
內(nèi)存泄漏在安卓app中十分常見,小內(nèi)存泄漏的積累會導(dǎo)致應(yīng)用內(nèi)存不足,并導(dǎo)致OOM崩潰。LeakCanary將幫助我們在開發(fā)期間找到這些內(nèi)存泄漏。
使用LeakCanary十分簡單,只需要找到·build.gradle·,并在·dependencies·中加入引用即可。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.1'
}
我們可以通過過濾LeakCanary標簽在Logcat中查看。
分析內(nèi)存泄漏
當發(fā)生內(nèi)存泄漏的時候,LeakCanary會自動幫你保存內(nèi)存泄漏信息,并將內(nèi)存泄漏相關(guān)的代碼以另一個app的形式展示給你,你可以根據(jù)提示,修改代碼,從而解決內(nèi)存泄漏的問題。
如何解決內(nèi)存泄漏呢
內(nèi)存泄漏常常存在的原因是因為兩個或多個對象生命周期不同,同時存在相互引用。導(dǎo)致生命周期短的對象被生命周期長的對象引用后無法正?;厥?,從而造成內(nèi)存泄漏。
下面是一個可能經(jīng)常遇見的內(nèi)存泄漏的小例子:
-
Activity內(nèi)存泄漏
安卓開發(fā)中,我們時常會把Activity當做Context傳入某些單例如UserInfo等類中,而我們知道,單例的生命周期可能是整個Application的生命周期,遠遠要比Activity的生命周期要長。如果使用在單例中某些成員變量保存了Activity的引用,當Activity被關(guān)閉的時候,就會導(dǎo)致內(nèi)存泄漏了。所以,當我們寫代碼的時候,要格外的慎重,如果Context不是必須傳入Activity,我們可以將Context傳入Application的Context。如果實在非要傳入Activity,你可以在使用完Activity只有,將相關(guān)的成員變量置空,這個時候,如果發(fā)成GC,Activity的引用計數(shù)為0,自然就能正常GC了。
這應(yīng)該是年前寫的最后一篇了,希望這篇文章能夠幫到你。