Android:內(nèi)存泄漏總結(jié)

Java 程序運(yùn)行時的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配堤框,對應(yīng)的,三種存儲策略使用的內(nèi)存空間主要分別是靜態(tài)存儲區(qū)(也稱方法區(qū))纵柿、棧區(qū)和堆區(qū)蜈抓。

  • 靜態(tài)存儲區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量昂儒。這塊內(nèi)存在程序編譯時就已經(jīng)分配好资昧,并且在程序整個運(yùn)行期間都存在。

  • 棧區(qū) :當(dāng)方法被執(zhí)行時荆忍,方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類型、對象的引用)都在棧上創(chuàng)建撤缴,并在方法執(zhí)行結(jié)束時這些局部變量所持有的內(nèi)存將會自動被釋放刹枉。因為棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高屈呕,但是分配的內(nèi)存容量有限微宝。

  • 堆區(qū) : 又稱動態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時直接 new 出來的內(nèi)存虎眨,也就是對象的實例蟋软。這部分內(nèi)存在不使用時將會由 Java 垃圾回收器來負(fù)責(zé)回收。

棧與堆的區(qū)別:

  • 1.堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組嗽桩。

  • 2.棧內(nèi)存用來存放方法或者局部變量等

  • 3.堆是先進(jìn)先出岳守,后進(jìn)后出

  • 4.棧是后進(jìn)先出,先進(jìn)后出

demo:

public class Sample {
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}

Sample mSample3 = new Sample();

Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中碌冶,但 mSample2 指向的對象是存在于堆上的湿痢。 mSample3 指向的對象實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1扑庞,而它自己存在于棧中譬重。

結(jié)論:
局部變量的基本數(shù)據(jù)類型和引用存儲于棧中,引用的對象實體存儲于堆中罐氨⊥喂妫—— 因為它們屬于方法中的變量,生命周期隨方法而結(jié)束栅隐。

成員變量全部存儲與堆中(包括基本數(shù)據(jù)類型塔嬉,引用和引用的對象實體)—— 因為它們屬于類玩徊,類對象終究是要被new出來使用的。

2.Java內(nèi)存泄漏引起的原因

內(nèi)存泄漏是指無用對象(不再使用的對象)持續(xù)占有內(nèi)存或無用對象的內(nèi)存得不到及時釋放邑遏,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏佣赖。內(nèi)存泄露有時不嚴(yán)重且不易察覺,這樣開發(fā)者就不知道存在內(nèi)存泄露记盒,但有時也會很嚴(yán)重憎蛤,會提示你Out of memory。

Android中常見的內(nèi)存泄漏匯總

1.單例造成的內(nèi)存泄漏

由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長纪吮,所以如果使用不恰當(dāng)?shù)脑捔┟剩苋菀自斐蓛?nèi)存泄漏。比如下面一個典型的例子

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}

這是一個普通的單例模式碾盟,當(dāng)創(chuàng)建這個單例的時候棚辽,由于需要傳入一個Context,所以這個Context的生命周期的長短至關(guān)重要:

1冰肴、如果此時傳入的是 Application 的 Context屈藐,因為 Application 的生命周期就是整個應(yīng)用的生命周期,所以這將沒有任何問題熙尉。

2联逻、如果此時傳入的是 Activity 的 Context,當(dāng)這個 Context 所對應(yīng)的 Activity 退出時检痰,由于該 Context 的引用被單例對象所持有包归,其生命周期等于整個應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時它的內(nèi)存并不會被回收铅歼,這就造成泄漏了公壤。

2.匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程

非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
比如:有的時候我們可能會在啟動頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源椎椰,可能會出現(xiàn)這種寫法:

   public class MainActivity extends AppCompatActivity {
        private static TestResource mResource = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
        mManager = new TestResource();
        }
        //...
        }
        class TestResource {
        //...
        }
        }

這樣就在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例厦幅,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建慨飘,不過這種寫法卻會造成內(nèi)存泄漏慨削,因為非靜態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實例套媚,該實例的生命周期和應(yīng)用的一樣長缚态,這就導(dǎo)致了該靜態(tài)實例一直會持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正车塘觯回收玫芦。

3.匿名內(nèi)部類

    public class MainActivity extends Activity {
    ...
    Runnable ref1 = new MyRunable();
    Runnable ref2 = new Runnable() {
        @Override
        public void run() {

        }
    };
       ...
    }

ref1和ref2的區(qū)別是,ref2使用了匿名內(nèi)部類本辐。我們來看看運(yùn)行時這兩個引用的內(nèi)存:

image

可以看到桥帆,ref1沒什么特別的医增。

但ref2這個匿名類的實現(xiàn)對象里面多了一個引用:

this$0這個引用指向MainActivity.this,也就是說當(dāng)前的MainActivity實例會被ref2持有老虫,如果將這個引用再傳入一個異步線程叶骨,此線程和此Acitivity生命周期不一致的時候,就造成了Activity的泄露祈匙。

總結(jié):因為匿名內(nèi)部類handler是持有外面Activity的引用的

  • a 匿名內(nèi)部類忽刽、非靜態(tài)內(nèi)部類都會隱性持有外部類引用,而靜態(tài)內(nèi)部類和匿名類的靜態(tài)實例是不會持有外部類引用的夺欲。

  • b 遇到生命周期比activity長的情形跪帝,內(nèi)存泄漏一定是個隱患。

  • c 盡量新建一個文件定義類亦或者利用弱引用拿到外部類引用些阅。

4.Handler 造成的內(nèi)存泄漏

Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了

    public class SampleActivity extends Activity {

    private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
    }
    }

在該 SampleActivity 中聲明了一個延遲10分鐘執(zhí)行的消息 Message伞剑,mLeakyHandler 將其 push 進(jìn)了消息隊列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時市埋,延遲執(zhí)行任務(wù)的 Message 還會繼續(xù)存在于主線程中黎泣,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類缤谎,它會持有外部類的引用聘裁,在這里就是指 SampleActivity)。

修復(fù)方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類弓千,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無關(guān)了献起。同時通過弱引用的方式引入 Activity洋访,避免直接將 Activity 作為 context 傳進(jìn)去,見下面代碼:

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

5.資源未關(guān)閉造成的內(nèi)存泄漏

對于使用了BraodcastReceiver谴餐,ContentObserver姻政,F(xiàn)ile,游標(biāo) Cursor岂嗓,Stream汁展,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷厌殉,否則這些資源將不會被回收食绿,造成內(nèi)存泄漏。

6.盡量避免使用 static 成員變量

如果成員變量被聲明為 static公罕,那我們都知道其生命周期將與整個app進(jìn)程生命周期一樣器紧。

這會導(dǎo)致一系列問題,如果你的app進(jìn)程設(shè)計上是長駐內(nèi)存的楼眷,那即使app切到后臺铲汪,這部分內(nèi)存也不會被釋放熊尉。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制,占內(nèi)存較大的后臺進(jìn)程將優(yōu)先回收掌腰,yi'wei如果此app做過進(jìn)程互保闭。活,那會造成app在后臺頻繁重啟齿梁。當(dāng)手機(jī)安裝了你參與開發(fā)的app以后一夜時間手機(jī)被消耗空了電量催植、流量,你的app不得不被用戶卸載或者靜默士飒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末查邢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酵幕,更是在濱河造成了極大的恐慌扰藕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芳撒,死亡現(xiàn)場離奇詭異邓深,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笔刹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門淆九,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人焕济,你說我怎么就攤上這事澈蚌。” “怎么了日月?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵袱瓮,是天一觀的道長。 經(jīng)常有香客問我爱咬,道長尺借,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任精拟,我火速辦了婚禮燎斩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜂绎。我一直安慰自己栅表,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布师枣。 她就那樣靜靜地躺著谨读,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坛吁。 梳的紋絲不亂的頭發(fā)上劳殖,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天铐尚,我揣著相機(jī)與錄音,去河邊找鬼哆姻。 笑死宣增,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矛缨。 我是一名探鬼主播爹脾,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼箕昭!你這毒婦竟也來了灵妨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤落竹,失蹤者是張志新(化名)和其女友劉穎泌霍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體述召,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朱转,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了积暖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藤为。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夺刑,靈堂內(nèi)的尸體忽然破棺而出缅疟,到底是詐尸還是另有隱情,我是刑警寧澤遍愿,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布存淫,位于F島的核電站,受9級特大地震影響错览,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煌往,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一倾哺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刽脖,春花似錦羞海、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至院水,卻和暖如春腊徙,著一層夾襖步出監(jiān)牢的瞬間简十,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工撬腾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留螟蝙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓民傻,卻偏偏與公主長得像胰默,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漓踢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355