android 內(nèi)存泄漏分析與優(yōu)化(二)

內(nèi)存抖動(dòng)宜猜、內(nèi)存溢出、內(nèi)存泄漏

  • 內(nèi)存抖動(dòng)
    在極短的時(shí)間內(nèi)硝逢,分配大量的內(nèi)存姨拥,然后又釋放它,這種現(xiàn)象就會(huì)造成內(nèi)存抖動(dòng)渠鸽。典型地叫乌,在 View 控件的 onDraw 方法里分配大量內(nèi)存,又釋放大量內(nèi)存徽缚,這種做法極易引起內(nèi)存抖動(dòng)憨奸,從而導(dǎo)致性能下降。因?yàn)?onDraw 里的大量內(nèi)存分配和釋放會(huì)給系統(tǒng)堆空間造成壓力凿试,觸發(fā) GC 工作去釋放更多可用內(nèi)存排宰,而 GC 工作起來時(shí),又會(huì)吃掉寶貴的幀時(shí)間 (幀時(shí)間是 16ms) 那婉,最終導(dǎo)致性能問題板甘。GC工作是發(fā)生在主線程中的,因?yàn)轭l繁的觸發(fā)GC導(dǎo)致掉幀就是內(nèi)存抖動(dòng)详炬。
  • 內(nèi)存溢出
    個(gè)Android應(yīng)用程序都執(zhí)行在自己的虛擬機(jī)中虾啦,每個(gè)虛擬機(jī)必定會(huì)有堆內(nèi)存閾值限制(值得一提的是這個(gè)閾值一般都由廠商依據(jù)硬件配置及設(shè)備特性自己設(shè)定,沒有統(tǒng)一標(biāo)準(zhǔn)痕寓,可以為64M傲醉,也可以為128M等;它的配置是在Android的屬性系統(tǒng)的/system/build.prop中配置dalvik.vm.heapsize=128m即可呻率,若存在dalvik.vm.heapstartsize則表示初始申請大小硬毕,也可以通過ActivityManager.getMemoryClass()獲得這個(gè)值),也即一個(gè)應(yīng)用進(jìn)程同時(shí)存在的對象必須小于閾值規(guī)定的內(nèi)存大小才可以正常運(yùn)行礼仗,否則則會(huì)報(bào)oom(OutOfMemoryError)吐咳。產(chǎn)生的原因最直接的原因是一下子申請大量的內(nèi)存超出閾值直接崩潰逻悠,還有原因是因?yàn)殄e(cuò)誤的程序?qū)е聝?nèi)存泄漏,即使申請一小段內(nèi)存也會(huì)直接崩潰韭脊。
  • 內(nèi)存泄漏
    內(nèi)存泄漏是本該由GC回收的內(nèi)存因?yàn)槟承┰虻貌坏交厥斩鴮?dǎo)致的童谒,通俗的講是本應(yīng)該被回收的對象被比它生命周期還有長的對象持有,導(dǎo)致不可回收沪羔,就產(chǎn)生了內(nèi)存泄漏饥伊。
    舉個(gè)例子一(單例最常見的內(nèi)存泄漏):
public final class MainActivity extends Activity
 { 
    private DbManager mDbManager; 
    @Override
    protected void onCreate(Bundle savedInstanceState) 
     { 
       super.onCreate(savedInstanceState); 
       setContentView(R.layout.activity_main); 
       //DbManager是一個(gè)單例模式類,這樣就持有了              
        //MainActivity引用蔫饰,導(dǎo)致泄露 
     mDbManager = DbManager.getInstance(this); 
    }
}

分析:由于單例的靜態(tài)特性使得它的生命周期比較長琅豆,又因?yàn)樗钟衋ctivity,所以當(dāng)activity退出時(shí)篓吁,此activity得不到GC回收從而導(dǎo)致了內(nèi)存泄漏茫因。

舉例二:集合中

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) 
{ 
    Object o = new Object(); 
     v.add(o);
     o = null; 
}

分析:
在這個(gè)例子中,我們循環(huán)申請Object對象杖剪,并將所申請的對象放入一個(gè) Vector 中冻押,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對象盛嘿,所以這個(gè)對象對 GC 來說是不可回收的翼雀。因此,如果對象加入到Vector 后孩擂,還必須從 Vector 中刪除狼渊,最簡單的方法就是將 Vector 對象設(shè)置為 null。

Android中常見的內(nèi)存泄漏匯總以及相應(yīng)的解決辦法

  • 集合類
    如果僅僅有添加元素的方法类垦,而沒有相應(yīng)的刪除機(jī)制狈邑,導(dǎo)致內(nèi)存被占用。如果這個(gè)集合類是全局性的變量 (比如類中的靜態(tài)屬性蚤认,全局性的 map 等即有靜態(tài)引用或 final 一直指向它)米苹,那么沒有相應(yīng)的刪除機(jī)制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減砰琢。比如上面的典型例子就是其中一種情況蘸嘶。
  • 單例造成的內(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; 
   } 
}

分析:這是一個(gè)普通的單例模式挚冤,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候况增,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長短至關(guān)重要训挡,如果此時(shí)傳入的是 Application 的 Context澳骤,因?yàn)锳pplication 的生命周期就是整個(gè)應(yīng)用的生命周期歧强,所以這將沒有任何問題。如果此時(shí)傳入的是 Activity 的 Context为肮,當(dāng)這個(gè) Context 所對應(yīng)的 Activity 退出時(shí)摊册,由于該 Context 的引用被單例對象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期颊艳,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收茅特,這就造成泄漏了。

正確的方式應(yīng)該改為下面這種方式:

public class AppManager
{ 
   private static AppManager instance; 
   private Context context;
   private AppManager(Context context) 
    { 
      this.context = context.getApplicationContext(); 
    }
   public static AppManager getInstance(Context context)
    { 
        if (instance == null) 
    {
       instance = new AppManager(context);
    } 
      return instance; 
    } 
 }
  • 匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程
    舉個(gè)例子
public class MainActivity extends Activity 
{
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() 
{ 
@Override
 public void run()
 {
 }
};
 ...
}

分析:匿名內(nèi)部類是默認(rèn)持有外部的引用籽暇,因此容易造成內(nèi)存泄漏。

  • Handler 使用不當(dāng)造成的內(nèi)存泄漏
    Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了饭庞,很多時(shí)候我們?yōu)榱吮苊?ANR 而不在主線程進(jìn)行耗時(shí)操作戒悠,在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請求回調(diào)等api都借助Handler來處理,但 Handler 不是萬能的舟山,對于 Handler 的使用代碼編寫一不規(guī)范即有可能造成內(nèi)存泄漏绸狐。另外,我們知道 Handler累盗、Message 和 MessageQueue 都是相互關(guān)聯(lián)在一起的寒矿,萬一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對象將被線程 MessageQueue 一直持有若债。 由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的符相。因此這種實(shí)現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無法正確釋放蠢琳。

舉個(gè)栗子:


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 中聲明了一個(gè)延遲10分鐘執(zhí)行的消息 Message啊终,mLeakyHandler 將其 push 進(jìn)了消息隊(duì)列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時(shí)傲须,延遲執(zhí)行任務(wù)的 Message 還會(huì)繼續(xù)存在于主線程中蓝牲,它持有該 Activity 的 Handler 引用,所以此時(shí) finish() 掉的 Activity 就不會(huì)被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類泰讽,它會(huì)持有外部類的引用例衍,在這里就是指 SampleActivity)。

修復(fù)方法:
在 Activity 中避免使用非靜態(tài)內(nèi)部類已卸,比如上面我們將 Handler 聲明為靜態(tài)的佛玄,則其存活期跟 Activity 的生命周期就無關(guān)了。同時(shí)通過弱引用的方式引入 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();
 }
}

綜述,即推薦使用靜態(tài)內(nèi)部類 + WeakReference 這種方式永乌。每次使用前注意判空惑申。

  • 資源未關(guān)閉造成的內(nèi)存泄漏
    對于使用了BraodcastReceiver具伍,ContentObserver,F(xiàn)ile圈驼,游標(biāo) Cursor人芽,Stream,Bitmap等資源的使用绩脆,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷萤厅,否則這些資源將不會(huì)被回收澄惊,造成內(nèi)存泄漏旱幼。

  • 一些不良代碼造成的內(nèi)存壓力
    比如:
    1 都弹、Bitmap 沒調(diào)用 recycle()方法潘拨,對于 Bitmap 對象在不使用時(shí),我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存陶贼,然后才它設(shè)置為 null. 因?yàn)榧虞d Bitmap 對象的內(nèi)存空間蟀伸,一部分是 java 的狼钮,一部分 C 的(因?yàn)?Bitmap 分配的底層是通過 JNI 調(diào)用的 )县好。 而這個(gè) recyle() 就是針對 C 部分的內(nèi)存釋放主守。
    2 禀倔、構(gòu)造 Adapter 時(shí),沒有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView参淫。這里推薦使用 ViewHolder

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末救湖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子涎才,更是在濱河造成了極大的恐慌鞋既,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耍铜,死亡現(xiàn)場離奇詭異涛救,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)业扒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門检吆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人程储,你說我怎么就攤上這事蹭沛。” “怎么了章鲤?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵摊灭,是天一觀的道長。 經(jīng)常有香客問我败徊,道長帚呼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮煤杀,結(jié)果婚禮上眷蜈,老公的妹妹穿的比我還像新娘。我一直安慰自己沈自,他們只是感情好酌儒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枯途,像睡著了一般忌怎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酪夷,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天榴啸,我揣著相機(jī)與錄音,去河邊找鬼晚岭。 笑死鸥印,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腥例。 我是一名探鬼主播辅甥,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼酝润,長吁一口氣:“原來是場噩夢啊……” “哼燎竖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起要销,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤构回,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后疏咐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纤掸,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年浑塞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了借跪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酌壕,死狀恐怖掏愁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卵牍,我是刑警寧澤果港,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站糊昙,受9級(jí)特大地震影響辛掠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一萝衩、第九天 我趴在偏房一處隱蔽的房頂上張望回挽。 院中可真熱鬧,春花似錦欠气、人聲如沸厅各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽队塘。三九已至,卻和暖如春宜鸯,著一層夾襖步出監(jiān)牢的瞬間憔古,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國打工淋袖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸿市,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓即碗,卻偏偏與公主長得像焰情,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子剥懒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題内舟。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講初橘,...
    宇宙只有巴掌大閱讀 2,363評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題验游。內(nèi)存泄漏...
    _痞子閱讀 1,639評(píng)論 0 8
  • 被文同時(shí)發(fā)布在CSDN上,歡迎查看保檐。 APP內(nèi)存的使用耕蝉,是評(píng)價(jià)一款應(yīng)用性能高低的一個(gè)重要指標(biāo)。雖然現(xiàn)在智能手機(jī)的內(nèi)...
    大圣代閱讀 4,829評(píng)論 2 54
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題夜只。內(nèi)存泄漏...
    apkcore閱讀 1,222評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題垒在。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講扔亥,...
    DreamFish閱讀 793評(píng)論 0 5