中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

系統(tǒng)剖析 Android 中的內(nèi)存泄漏

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

作為Android開發(fā)人員,我們或多或少都聽說(shuō)過(guò)內(nèi)存泄漏。那么何為內(nèi)存泄漏,Android中的內(nèi)存泄漏又是什么樣子的呢,本文將簡(jiǎn)單概括的進(jìn)行一些總結(jié)。

關(guān)于內(nèi)存泄露的定義,我可以理解成這樣

沒有用的對(duì)象無(wú)法回收的現(xiàn)象就是內(nèi)存泄露

如果程序發(fā)生了內(nèi)存泄露,則會(huì)帶來(lái)如下的問題

  • 應(yīng)用可用的內(nèi)存減少,增加了堆內(nèi)存的壓力
  • 降低了應(yīng)用的性能,比如會(huì)觸犯更頻繁的GC
  • 嚴(yán)重的時(shí)候可能會(huì)導(dǎo)致內(nèi)存溢出錯(cuò)誤,即OOM Error

在正式介紹內(nèi)存泄露之前,我們有必要介紹一些必要的預(yù)備知識(shí)。

預(yù)備知識(shí)1: Java中的對(duì)象

  • 當(dāng)我們使用 new 指令生成對(duì)象時(shí),堆內(nèi)存將會(huì)為此開辟一份空間存放該對(duì)象
  • 創(chuàng)建的對(duì)象可以被局部變量,實(shí)例變量和類變量引用。
  • 通常情況下,類變量持有的對(duì)象生命周期最長(zhǎng),實(shí)例變量次之,局部變量最短。
  • 垃圾回收器回收非存活的對(duì)象,并釋放對(duì)應(yīng)的內(nèi)存空間。

預(yù)備知識(shí)2:Java中的GC

  • 和C++不同,對(duì)象的釋放不需要手動(dòng)完成,而是由垃圾回收器自動(dòng)完成。
  • 垃圾回收器運(yùn)行在JVM中
  • 通常GC有兩種算法:引用計(jì)數(shù)和GC根節(jié)點(diǎn)遍歷

引用計(jì)數(shù)

  • 每個(gè)對(duì)象有對(duì)應(yīng)的引用計(jì)數(shù)器
  • 當(dāng)一個(gè)對(duì)象被引用(被復(fù)制給變量,傳入方法中),引用計(jì)數(shù)器加1
  • 當(dāng)一個(gè)對(duì)象不被引用(離開變量作用域),引用計(jì)數(shù)器就會(huì)減1
  • 基于這種算法的垃圾回收器效率較高
  • 循環(huán)引用的問題引用計(jì)數(shù)算法的垃圾回收器無(wú)法解決。
  • 主流的JVM很少使用基于這種算法的垃圾回收器實(shí)現(xiàn)。

GC根節(jié)點(diǎn)遍歷

  • 識(shí)別對(duì)象為垃圾從被稱為GC 根節(jié)點(diǎn)出發(fā)
  • 每一個(gè)被遍歷的強(qiáng)引用可到達(dá)對(duì)象,都會(huì)被標(biāo)記為存活
  • 在遍歷結(jié)束后,沒有被標(biāo)記為存活的對(duì)象都被視為垃圾,需要后續(xù)進(jìn)行回收處理
  • 主流的JVM一般都采用這種算法的垃圾回收器實(shí)現(xiàn)

以上圖為例,我們可以知道

  • 最下層的兩個(gè)節(jié)點(diǎn)為GC Roots,即GC Tracing的起點(diǎn)
  • 中間的一層的對(duì)象,可以強(qiáng)引用到達(dá)GC根節(jié)點(diǎn),所以被標(biāo)記為存活
  • 最上層的三個(gè)對(duì)象,無(wú)法強(qiáng)引用達(dá)到GC根節(jié)點(diǎn),所以無(wú)法標(biāo)記為存活,也就是所謂的垃圾,需要被后續(xù)回收掉。

上面的垃圾回收中,我們提到的兩個(gè)概念,一個(gè)是GC根節(jié)點(diǎn),另一個(gè)是強(qiáng)引用

在Java中,可以作為GC 根節(jié)點(diǎn)的有

  • 類,由系統(tǒng)類加載器加載的類。這些類從不會(huì)被卸載,它們可以通過(guò)靜態(tài)屬性的方式持有對(duì)象的引用。注意,一般情況下由自定義的類加載器加載的類不能成為GC Roots
  • 線程,存活的線程
  • Java方法棧中的局部變量或者參數(shù)
  • JNI方法棧中的局部變量或者參數(shù)
  • JNI全局引用
  • 用做同步監(jiān)控的對(duì)象
  • 被JVM持有的對(duì)象,這些對(duì)象由于特殊的目的不被GC回收。這些對(duì)象可能是系統(tǒng)的類加載器,一些重要的異常處理類,一些為處理異常預(yù)留的對(duì)象,以及一些正在執(zhí)行類加載的自定義的類加載器。但是具體有哪些前面提到的對(duì)象依賴于具體的JVM實(shí)現(xiàn)。

提到強(qiáng)引用,有必要系統(tǒng)說(shuō)一下Java中的引用類型。Java中的引用類型可以分為一下四種:

  • 強(qiáng)引用: 默認(rèn)的引用類型,例如 StringBuffer buffer = new StringBuffer(); 就是buffer變量持有的為StringBuilder的強(qiáng)引用類型。
  • 軟引用:即SoftReference,其指向的對(duì)象只有在內(nèi)存不足的時(shí)候進(jìn)行回收。
  • 弱引用:即WeakReference,其指向的對(duì)象在GC執(zhí)行時(shí)會(huì)被回收。
  • 虛引用:即PhantomReference,與ReferenceQueue結(jié)合,用作記錄該引用指向的對(duì)象已被銷毀。

補(bǔ)充了預(yù)備知識(shí),我們就需要具體講一講Android中的內(nèi)存泄漏了。

Android中的內(nèi)存泄漏

歸納而言,Android中的內(nèi)存泄漏有以下幾個(gè)特點(diǎn):

  • 相對(duì)而言,Android中的內(nèi)存泄漏更加容易出現(xiàn)。
  • 由于Android系統(tǒng)為每個(gè)App分配的內(nèi)存空間有限,在一個(gè)內(nèi)存泄漏嚴(yán)重的App中,很容易導(dǎo)致OOM,即內(nèi)存溢出錯(cuò)誤。
  • 內(nèi)存泄漏會(huì)隨著App的推出而消失(即進(jìn)程結(jié)束)。

在Android中的內(nèi)存泄漏場(chǎng)景有很多,按照類型劃分可以歸納為

  • 長(zhǎng)期持有(Activity)Context導(dǎo)致的
  • 忘記注銷監(jiān)聽器或者觀察者
  • 由非靜態(tài)內(nèi)部類導(dǎo)致的

此外,如果按照泄漏的程度,可以分為

  • 長(zhǎng)時(shí)間泄漏,即泄漏只能等待進(jìn)程退出才消失
  • 短時(shí)間泄漏,被泄漏的對(duì)象后續(xù)會(huì)被回收掉。

長(zhǎng)時(shí)間持有Activity實(shí)例

在Android中,Activity是我們常用的組件,通常情況下,一個(gè)Activity會(huì)包含了一些復(fù)雜的UI視圖,而視圖中如果含有ImageView,則有可能會(huì)使用比較大的Bitmap對(duì)象。因而一個(gè)Activity持有的內(nèi)存會(huì)相對(duì)很多,如果造成了Activity的泄漏,勢(shì)必造成一大塊內(nèi)存無(wú)法回收,發(fā)生泄漏。

這里舉個(gè)簡(jiǎn)單的例子,說(shuō)明Activity的內(nèi)存泄漏,比如我們有一個(gè)叫做AppSettings的類,它是一個(gè)單例模式的應(yīng)用。

public class AppSettings {
    private Context mAppContext;
    private static AppSettings sInstance = new AppSettings();

    //some other codes
    public static AppSettings getInstance() {
      return sInstance;
    }

    public final void setup(Context context) {
        mAppContext = context;
    }
}

當(dāng)我們傳入Activity作為Context參數(shù)時(shí),則AppSettings實(shí)例會(huì)持有這個(gè)Activity的實(shí)例。

當(dāng)我們旋轉(zhuǎn)設(shè)備時(shí),Android系統(tǒng)會(huì)銷毀當(dāng)前的Activity,創(chuàng)建新的Activity來(lái)加載合適的布局。如果出現(xiàn)Activity被單例實(shí)例持有,那么旋轉(zhuǎn)過(guò)程中的舊Activity無(wú)法被銷毀掉。就發(fā)生了我們所說(shuō)的內(nèi)存泄漏。

想要解決這個(gè)問題也不難,那就是使用Application的Context對(duì)象,因?yàn)樗虯ppSettings實(shí)例具有相同的生命周期。這里是通過(guò)使用 Context.getApplicationContext() 方法來(lái)實(shí)現(xiàn)。所以修改如下

public class AppSettings {
    private Context mAppContext;
    private static AppSettings sInstance = new AppSettings();

    //some other codes
    public static AppSettings getInstance() {
      return sInstance;
    }

    public final void setup(Context context) {
        mAppContext = context.getApplicationContext();
    }
}

忘記反注冊(cè)監(jiān)聽器

在Android中我們會(huì)使用很多l(xiāng)istener,observer。這些都是作為觀察者模式的實(shí)現(xiàn)。當(dāng)我們注冊(cè)一個(gè)listener時(shí),這個(gè)listener的實(shí)例會(huì)被主題所引用。如果主題的生命周期要明顯大于listener,那么就有可能發(fā)生內(nèi)存泄漏。

以下面的代碼為例

public class MainActivity extends AppCompatActivity implements OnNetworkChangedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        NetworkManager.getInstance().registerListener(this);
    }

    @Override
    public void onNetworkUp() {

    }

    @Override
    public void onNetworkDown() {

    }
}

上述代碼處理的業(yè)務(wù),可以理解為

  • AppCompatActivity實(shí)現(xiàn)了OnNetworkChangedListener接口,用來(lái)監(jiān)聽網(wǎng)絡(luò)的可用性變化
  • NetworkManager為單例模式實(shí)現(xiàn),其registerListener接收了MainActivity實(shí)例

又是單例模式,可知NetworkManager會(huì)持有MainActivity的實(shí)例引用,因而屏幕旋轉(zhuǎn)時(shí),MainActivity同樣無(wú)法被回收,進(jìn)而造成了內(nèi)存泄漏。

對(duì)于這種類型的內(nèi)存泄漏,解決方法是這樣的。即在MainActivity的onDestroy方法中加入反注銷的方法調(diào)用。

public class MainActivity extends AppCompatActivity implements OnNetworkChangedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        NetworkManager.getInstance().registerListener(this);
    }

    @Override
    public void onNetworkUp() {

    }

    @Override
    public void onNetworkDown() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        NetworkManager.getInstance().unregisterListener(this);
    }

}

非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄漏

在Java中,非靜態(tài)內(nèi)部類會(huì)隱式持有外部類的實(shí)例引用。想要了解更多,可以參考這篇文章 細(xì)話Java:”失效”的private修飾符

通常情況下,我們會(huì)書寫類似這樣的代碼

public class SensorListenerActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SensorManager sensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
        sensorManager.registerListener(new SensorListener() {
            @Override
            public void onSensorChanged(int sensor, float[] values) {

            }

            @Override
            public void onAccuracyChanged(int sensor, int accuracy) {

            }
        }, SensorManager.SENSOR_ALL);
    }
}

其中上面的SensorListner實(shí)例是一個(gè)匿名內(nèi)部類的實(shí)例,也是非靜態(tài)內(nèi)部類的一種。因此SensorListner也會(huì)持有外部SensorListenerActivity的實(shí)例引用。

而SensorManager作為單例模式實(shí)現(xiàn),其生命周期與Application相同,和SensorListner對(duì)象生命周期不同,有可能間接導(dǎo)致SensorListenerActivity發(fā)生內(nèi)存泄漏。

解決這種問題的方法可以是

  • 使用實(shí)例變量存儲(chǔ)SensonListener實(shí)例,在Activity的onDestroy方法進(jìn)行反注冊(cè)。
  • 如果registerListener方法可以修改,可以使用弱引用或者WeakHashMap來(lái)解決。

除了上面的三種場(chǎng)景外,Android的內(nèi)存泄漏還有可能出現(xiàn)在以下情況

  • 使用 Activity.getSystemService() 使用不當(dāng),也會(huì)導(dǎo)致內(nèi)存泄漏。
  • 資源未關(guān)閉也會(huì)造成內(nèi)存泄漏
  • Handler使用不當(dāng)也可以造成內(nèi)存泄漏的發(fā)生
  • 延遲的任務(wù)也可能導(dǎo)致內(nèi)存泄漏

解決內(nèi)存泄漏

想要解決內(nèi)存泄漏無(wú)非如下兩種方法

  • 手動(dòng)解除不必要的強(qiáng)引用關(guān)系
  • 使用弱引用或者軟引用替換強(qiáng)引用關(guān)系

下面會(huì)簡(jiǎn)單介紹一些內(nèi)存泄漏檢測(cè)和解決的工具

Strictmode

  • StrictMode,嚴(yán)格模式,是Android中的一種檢測(cè)VM和線程違例的工具。
  • 使用 detectAll() 或者 detectActivityLeaks() 可以檢測(cè)Activity的內(nèi)存泄漏
  • 使用 setClassInstanceLimit() 可以限定類的實(shí)例個(gè)數(shù),可以輔助判斷某些類是否發(fā)生了內(nèi)存泄漏
  • 但是StrictMode只能檢測(cè)出現(xiàn)象,并不能提供更多具體的信息。
  • 了解更多關(guān)于StrictMode,請(qǐng)?jiān)L問 Android性能調(diào)優(yōu)利器StrictMode

Android Memory Monitors

Android Memory Monitor內(nèi)置于Android Studio中,用于展示應(yīng)用內(nèi)存的使用和釋放情況。它大致長(zhǎng)成這個(gè)樣子

當(dāng)你的App占用的內(nèi)存持續(xù)增加,而且你同時(shí)出發(fā)GC,也沒有進(jìn)行釋放,那么你的App很有可能發(fā)生了內(nèi)存泄漏問題。

LeakCanary

  • LeakCanary是一個(gè)檢測(cè)Java和Android內(nèi)存泄漏的庫(kù)
  • 由Square公司開發(fā)
  • 集成LeakCanary之后,只需要等待內(nèi)存泄漏出現(xiàn)就可以了,無(wú)需認(rèn)為進(jìn)行主動(dòng)檢測(cè)。
  • 關(guān)于如何使用LeakCanary,可以參考這篇文章 Android內(nèi)存泄漏檢測(cè)利器:LeakCanary

Heap Dump

  • 一個(gè)Heap dump就是某一時(shí)間點(diǎn)的內(nèi)存快照
  • 它包含了某個(gè)時(shí)間點(diǎn)的Java對(duì)象和類信息。
  • 我們可以通上述提到的Android Heap Monitor進(jìn)行Heap Dump,當(dāng)然LeakCanary也會(huì)生成Heap Dump文件。
  • 生成的Heap Dump文件擴(kuò)展名為.hprof 即Heap Profile.
  • 通常情況下,一個(gè)heap profile需要轉(zhuǎn)換后才能被MAT使用分析。

Shallow Heap VS Retained Heap

  • Shallow Heap 指的是對(duì)象自身的占用的內(nèi)存大小。
  • 對(duì)象x的Retained Set指的是如果對(duì)象x被GC移除,可以釋放總的對(duì)象的集合。
  • 對(duì)象x的Retained Heap指的就是上述x的Retained Set的占用內(nèi)存大小。

以上圖做個(gè)例子,進(jìn)行分析

  • A,B,C,D四個(gè)對(duì)象的Shallow Heap均為1M
  • B,C,D的Retained Heap均為1M
  • A的Retained Heap為4M

真實(shí)情況下如何計(jì)算泄漏內(nèi)存大小

上述的Retained Heap的大小獲取是基于假設(shè)的,而現(xiàn)實(shí)在進(jìn)行分析中不可能基于這種方法,那么實(shí)際上計(jì)算泄漏內(nèi)存的大小的方法其實(shí)是這樣的。

這里我們需要一個(gè)概念,就是Dominator Tree(統(tǒng)治者樹)。

  • 如果對(duì)象x統(tǒng)治對(duì)象y,那么每條從GC根節(jié)點(diǎn)到y(tǒng)對(duì)象的路徑都會(huì)經(jīng)過(guò)x,即x是GC根節(jié)點(diǎn)到y(tǒng)的必經(jīng)之路。
  • 上述情況下,我們可以說(shuō)x是y的統(tǒng)治者
  • 最近統(tǒng)治者指的是離對(duì)象y最近的統(tǒng)治者。

上圖中

  • A和B都不無(wú)法統(tǒng)治C對(duì)象,即C對(duì)象被A和B的父對(duì)象統(tǒng)治
  • H不受F,G,D,E統(tǒng)治,但是受C統(tǒng)治
  • F和D是循環(huán)引用,但是按照路徑的方向(從根節(jié)點(diǎn)到對(duì)象),D統(tǒng)治F

內(nèi)存泄漏與OOM

  • OOM全稱Out Of Memory Error 內(nèi)存溢出錯(cuò)誤
  • OOM發(fā)生在,當(dāng)我們嘗試進(jìn)行創(chuàng)建對(duì)象,但是堆內(nèi)存無(wú)法通過(guò)GC釋放足夠的空間,堆內(nèi)存也無(wú)法在繼續(xù)增長(zhǎng),從而完成對(duì)象創(chuàng)建請(qǐng)求,所以發(fā)生了OOM
  • OOM發(fā)生很有可能是內(nèi)存泄漏導(dǎo)致
  • 但是并非所有的OOM都是由內(nèi)存泄漏引起
  • 內(nèi)存泄漏也并不一定引起OOM

聲明

  • 其中第一張圖片GC回收?qǐng)D來(lái)自Patrick Dubroy在Google IO的演講Keynote
  • 最后一張Dorminator Tree來(lái)自MAT官方網(wǎng)站

一些鏈接

  • 垃圾回收器如何處理循環(huán)引用
  • 譯文:理解Java中的弱引用
  • Android中Handler引起的內(nèi)存泄露
  • 避免Android中Context引起的內(nèi)存泄露
  • Google為何這樣設(shè)計(jì)OnSharedPreferenceChangeListener
  • Keynote下載地址

最后的話

內(nèi)存泄漏在App中很常見,需要我們花時(shí)間去解決。

處理內(nèi)存泄漏問題,不僅要解決掉,更應(yīng)該善于整理總結(jié),做到后續(xù)編碼中主動(dòng)避免。

 

來(lái)自:http://droidyue.com/blog/2016/11/23/memory-leaks-in-android/

 

標(biāo)簽: Google 代碼 網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:APP啟動(dòng)慢怎么辦,Android官方這樣說(shuō)

下一篇:Go 語(yǔ)言的 10 個(gè)實(shí)用技巧