每日一道面試題(第3期)---通常什麼狀況下會致使內存泄漏問題

零零碎碎的東西老是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無心中發現了這個每日一道面試題,想了想若是隻是簡單地去思考,那麼不只會收效甚微,甚至難一點的題目本身可能都懶得去想,堅持不下來。因此不如把每一次的思考、理解以及別人的看法記錄下來。不只加深本身的理解,更要激勵本身堅持下去。java

內存泄漏

定義

當本應該被釋放或無用的對象,由於被其餘存活的對象持有其引用,致使該對象不能被垃圾回收器回收,一直佔用着內存,使程序運行變得緩慢甚至崩潰。git

緣由

爲何被其餘存活的對象持有其引用,就不能被回收?這個就須要瞭解java的垃圾回收機制。github

java垃圾回收機制面試

什麼樣的對象會被認爲須要回收呢?咱們如今將每個對象看做有向圖的結點,而對象之間的引用關係則是有向圖的邊。那麼必定會有一個起始結點對象,若是這個對象是框架

  • 方法區的類靜態屬性引用的對象
  • 方法區中的常量引用的對象
  • 本地方法棧中的native方法引用的對象
  • 虛擬機棧(棧幀中的本地變量表(局部變量表))所引用的對象

那麼由此對象能夠在有向圖上遍歷到的全部對象都不會被回收。反之,就會被認爲是要回收的對象。ide

抽象的來講,一個程序中會存在許多這樣的有向圖,若是一個對象同時被兩個存在起始結點對象的有向圖所引用。當一個有向圖完成使命,須要被銷燬,但另外一個有向圖的生命週期尚未結束。那麼這個本應該無用的對象,卻不能被垃圾回收器回收,只有當另外一個有向圖生命週期結束,纔會被回收。post

因此,就是咱們常說的生命週期不一樣的兩個對象間有引用關係,生命週期短的可能會形成內存泄漏,持續的時間取決於生命週期長的對象。若是這個對象是靜態變量,那麼將會持續到整個程序運行結束。學習

Android內存泄漏狀況

集合類

通常的集合類並不會形成內存泄漏,可是若是是全局性的集合類,若是不注意在使用完畢後進行remove操做,就極有可能形成內存泄露。this

單例模式

這裏的單例模式是指建立時須要傳入Context做爲參數。好比咱們常寫的下面這個代碼。spa

public class Manager {
    private static Manager instance;
    private Context context;
    private Manager(Context context){
        this.context = context;
    }

    public static Manager getInstance(Context context){
        if(instance == null){
            instance = new Manager(context);
        }
        return instance;
    }
}
複製代碼

關鍵就在於這個Context,若是這個Context是Activity的Content,那麼顯然Activity的生命週期和單例模式的對象的生命週期是不同的,傳入Content的Activity使用完畢須要被回收時,是沒法被垃圾回收器回收的。

顯而易見的,當這個Context是Application的時,就不存在內存泄漏的問題。由於單例模式的對象與Application的生命週期都是整個應用的生命週期,不會有任何問題。

因此,咱們能夠改成這樣寫

public class Manager {
    private static Manager instance;
    private Context context;
    private Manager(Context context){
        this.context = context.getApplicationContext();
    }

    public static Manager getInstance(Context context){
        if(instance == null){
            instance = new Manager(context);
        }
        return instance;
    }
}
複製代碼

固然了,Application的Context也不是能隨便用的。若是是要啓動一個Activity,Application須要建立一個新的Task任務棧。而若是是建立一個Dialog,則只有Activity的context才能夠。

匿名內部類

對於匿名內部類,在Android中典型的例子就是Handler了吧。這個我在第一期---自定義Handler如何有效保證內存泄漏問題已經說得很明白了。主要就是匿名內部類持有外部類的引用,匿名內部類的一些操做使得該內部類對象的生命週期和外部類的生命週期不相同,形成內存泄漏。

非靜態內部類

在開發中,咱們爲了程序的高效以及資源重複利用,咱們可能會常常寫出這樣的代碼。

public class MainActivity extends BaseActivity {
    private static Resource resource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(resource == null){
            resource = new Resource();
        }
    }

    class Resource{
    }
}
複製代碼

這樣作雖然有效的避免了資源的重複建立,每次在Activity啓動時快速的使用這些資源,但卻會形成內存泄漏。由於非靜態內部類也默認會持有外部類的引用。而因爲這個非靜態內部類的靜態實例,其生命週期會和整個應用程序同樣長,因此會形成內存泄露。

解決辦法就是將該內部類設爲靜態內部類,或者把這個內部類抽取出來封裝成一個單例模式。

資源未關閉

在咱們使用BroadcastReceiver、File、Course、Stream、ContentObserver等資源或者一些框架eventbus等明確表示須要Register與unRegister時,都應該在Activity被銷燬時關閉或者註銷,不然這些資源將不會被回收。

不良代碼形成的壓力

有時也並非不能及時回收的對象形成的內存泄漏,而是有些代碼沒有及時有效的釋放不須要使用的內存,或者是沒有對於現有資源沒有有效利用而頻繁的申請新的內存,形成內存的巨大壓力。

好比ListView中的ContentView,不使用ViewHolder有效的複用View而頻繁的建立新的View,形成內存壓力。

相關文章
相關標籤/搜索