精品伊人久久大香线蕉,开心久久婷婷综合中文字幕,杏田冲梨,人妻无码aⅴ不卡中文字幕

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Android 關(guān)于內(nèi)存泄露,你必須了解的東西

作者:developerHaoz
地址:https://www.jianshu.com/p/65f914e6a2f8
聲明:本文是 developerHaoz 原創(chuàng)投稿,轉(zhuǎn)發(fā)等請(qǐng)聯(lián)系原作者授權(quán)。

前言

內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)過(guò)程中有效避免我們的應(yīng)用程序出現(xiàn)內(nèi)存泄露的問(wèn)題。內(nèi)存泄露相信大家都不陌生,我們可以這樣理解:「沒(méi)有用的對(duì)象無(wú)法回收的現(xiàn)象就是內(nèi)存泄露」。

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

  • 應(yīng)用可用的內(nèi)存減少,增加了堆內(nèi)存的壓力

  • 降低了應(yīng)用的性能,比如會(huì)觸發(fā)更頻繁的 GC

  • 嚴(yán)重的時(shí)候可能會(huì)導(dǎo)致內(nèi)存溢出錯(cuò)誤,即 OOM Error

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)求的時(shí)候,OOM 發(fā)生很有可能是內(nèi)存泄露導(dǎo)致的,但并非所有的 OOM 都是由內(nèi)存泄露引起的,內(nèi)存泄露也并不一定引起 OOM。

一、基礎(chǔ)準(zhǔn)備

如果真的想比較清楚的了解內(nèi)存泄露的話,對(duì)于 Java 的內(nèi)存管理以及引用類型有一個(gè)清晰的認(rèn)識(shí)是必不可少的。

  • 理解 Java 的內(nèi)存管理能讓我們更深一層地了解 Java 虛擬機(jī)是怎樣使用內(nèi)存的,一旦出現(xiàn)內(nèi)存泄露,我們也能更加從容地排查問(wèn)題。

  • 了解 Java 的引用類型,能讓我們更加理解內(nèi)存泄露出現(xiàn)的原因,以及常見(jiàn)的解決方法。

具體的內(nèi)容,可以看下這篇文章 你真的懂 Java 的內(nèi)存管理和引用類型嗎?

二、Android 中內(nèi)存泄露的常見(jiàn)場(chǎng)景 & 解決方案

1、單例造成的內(nèi)存泄露

單例模式是非常常用的設(shè)計(jì)模式,使用單例模式的類,只會(huì)產(chǎn)生一個(gè)對(duì)象,這個(gè)對(duì)象看起來(lái)像是一直占用著內(nèi)存,但這并不意味著就是浪費(fèi)了內(nèi)存,內(nèi)存本來(lái)就是拿來(lái)裝東西的,只要這個(gè)對(duì)象一直都被高效的利用就不能叫做泄露。

但是過(guò)多的單例會(huì)讓內(nèi)存占用過(guò)多,而且單例模式由于其 靜態(tài)特性,其生命周期 = 應(yīng)用程序的生命周期,不正確地使用單例模式也會(huì)造成內(nèi)存泄露。

舉個(gè)例子:

public class SingleInstanceTest {
   private static SingleInstanceTest sInstance;
   private Context mContext;
   private SingleInstanceTest(Context context){
       this.mContext = context;
   }
   public static SingleInstanceTest newInstance(Context context){
       if(sInstance == null){
           sInstance = new SingleInstanceTest(context);
       }
       return sInstance;
   }
}

上面是一個(gè)比較簡(jiǎn)單的單例模式用法,需要外部傳入一個(gè) Context 來(lái)獲取該類的實(shí)例,如果此時(shí)傳入的 Context 是 Activity 的話,此時(shí)單例就有持有該 Activity 的強(qiáng)引用(直到整個(gè)應(yīng)用生命周期結(jié)束)。這樣的話,即使該 Activity 退出,該 Activity 的內(nèi)存也不會(huì)被回收,這樣就造成了內(nèi)存泄露,特別是一些比較大的 Activity,甚至還會(huì)導(dǎo)致 OOM(Out Of Memory)。

解決方法:單例模式引用的對(duì)象的生命周期 = 應(yīng)用生命周期

public class SingleInstanceTest {
   private static SingleInstanceTest sInstance;
   private Context mContext;
   private SingleInstanceTest(Context context){
       this.mContext = context.getApplicationContext();
   }
   public static SingleInstanceTest newInstance(Context context){
       if(sInstance == null){
           sInstance = new SingleInstanceTest(context);
       }
       return sInstance;
   }
}

可以看到在 SingleInstanceTest 的構(gòu)造函數(shù)中,將 context.getApplicationContext() 賦值給 mContext,此時(shí)單例引用的對(duì)象是 Application,而 Application 的生命周期本來(lái)就跟應(yīng)用程序是一樣的,也就不存在內(nèi)存泄露。

這里再拓展一點(diǎn),很多時(shí)候我們?cè)谛枰玫?Activity 或者 Context 的地方,會(huì)直接將 Activity 的實(shí)例作為參數(shù)傳給對(duì)應(yīng)的類,就像這樣:

public class Sample {
   private Context mContext;
   public Sample(Context context){
       this.mContext = context;
   }
   public Context getContext() {
       return mContext;
   }
}
// 外部調(diào)用
Sample sample = new Sample(MainActivity.this);

這種情況如果不注意的話,很容易就會(huì)造成內(nèi)存泄露,比較好的寫(xiě)法是使用弱引用(WeakReference)來(lái)進(jìn)行改進(jìn)。

public class Sample {
   private WeakReference<Context> mWeakReference;
   public Sample(Context context){
       this.mWeakReference = new WeakReference<>(context);
   }
   public Context getContext() {
       if(mWeakReference.get() != null){
           return mWeakReference.get();
       }
       return null;
   }
}
// 外部調(diào)用
Sample sample = new Sample(MainActivity.this);

被弱引用關(guān)聯(lián)的對(duì)象只能存活到下一次垃圾回收之前,也就是說(shuō)即使 Sample 持有 Activity 的引用,但由于 GC 會(huì)幫我們回收相關(guān)的引用,被銷毀的 Activity 也會(huì)被回收內(nèi)存,這樣我們就不用擔(dān)心會(huì)發(fā)生內(nèi)存泄露了。

2、非靜態(tài)內(nèi)部類 / 匿名類

我們先來(lái)看看非靜態(tài)內(nèi)部類(non static inner class)和 靜態(tài)內(nèi)部類(static inner class)之間的區(qū)別。

class 對(duì)比static inner classnon static inner class
與外部 class 引用關(guān)系如果沒(méi)有傳入?yún)?shù),就沒(méi)有引用關(guān)系自動(dòng)獲得強(qiáng)引用
被調(diào)用時(shí)需要外部實(shí)例不需要需要
能否調(diào)用外部 class 中的變量和方法不能
生命周期自主的生命周期依賴于外部類,甚至比外部類更長(zhǎng)

可以看到非靜態(tài)內(nèi)部類自動(dòng)獲得外部類的強(qiáng)引用,而且它的生命周期甚至比外部類更長(zhǎng),這便埋下了內(nèi)存泄露的隱患。如果一個(gè) Activity 的非靜態(tài)內(nèi)部類的生命周期比 Activity 更長(zhǎng),那么 Activity 的內(nèi)存便無(wú)法被回收,也就是發(fā)生了內(nèi)存泄露,而且還有可能發(fā)生難以預(yù)防的空指針問(wèn)題。

舉個(gè)例子:

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       new MyAscnyTask().execute();
   }
   class MyAscnyTask extends AsyncTask<Void, Integer, String>{
       @Override
       protected String doInBackground(Void... params) {
           try {
               Thread.sleep(5000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           return "";
       }
   }
}

可以看到我們?cè)?Activity 中繼承 AsyncTask 自定義了一個(gè)非靜態(tài)內(nèi)部類,在 doInbackground() 方法中做了耗時(shí)的操作,然后在 onCreate() 中啟動(dòng) MyAsyncTask。如果在耗時(shí)操作結(jié)束之前,Activity 被銷毀了,這時(shí)候因?yàn)?MyAsyncTask 持有 Activity 的強(qiáng)引用,便會(huì)導(dǎo)致 Activity 的內(nèi)存無(wú)法被回收,這時(shí)候便會(huì)產(chǎn)生內(nèi)存泄露。

解決方法:將 MyAsyncTask 變成非靜態(tài)內(nèi)部類

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       new MyAscnyTask().execute();
   }
   static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
       @Override
       protected String doInBackground(Void... params) {
           try {
               Thread.sleep(50000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           return "";
       }
   }
}

這時(shí)候 MyAsyncTask 不再持有 Activity 的強(qiáng)引用,即使 AsyncTask 的耗時(shí)操作還在繼續(xù),Activity 的內(nèi)存也能順利地被回收。

匿名類和非靜態(tài)內(nèi)部類最大的共同點(diǎn)就是 都持有外部類的引用,因此,匿名類造成內(nèi)存泄露的原因也跟靜態(tài)內(nèi)部類基本是一樣的,下面舉個(gè)幾個(gè)比較常見(jiàn)的例子:

public class MainActivity extends AppCompatActivity {
   private Handler mHandler = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
       }
   };
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       // ① 匿名線程持有 Activity 的引用,進(jìn)行耗時(shí)操作
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   Thread.sleep(50000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       // ② 使用匿名 Handler 發(fā)送耗時(shí)消息
       Message message = Message.obtain();
       mHandler.sendMessageDelayed(message, 60000);
   }

上面舉出了兩個(gè)比較常見(jiàn)的例子

  • new 出一個(gè)匿名的 Thread,進(jìn)行耗時(shí)的操作,如果 MainActivity 被銷毀而 Thread 中的耗時(shí)操作沒(méi)有結(jié)束的話,便會(huì)產(chǎn)生內(nèi)存泄露

  • new 出一個(gè)匿名的 Handler,這里我采用了 sendMessageDelayed() 方法來(lái)發(fā)送消息,這時(shí)如果 MainActivity 被銷毀,而 Handler 里面的消息還沒(méi)發(fā)送完畢的話,Activity 的內(nèi)存也不會(huì)被回收

解決方法:

  • 繼承 Thread  實(shí)現(xiàn)靜態(tài)內(nèi)部類

  • 繼承 Handler 實(shí)現(xiàn)靜態(tài)內(nèi)部類,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);

3、集合類

集合類添加元素后,仍引用著集合元素對(duì)象,導(dǎo)致該集合中的元素對(duì)象無(wú)法被回收,從而導(dǎo)致內(nèi)存泄露,舉個(gè)例子:

  static List<Object> objectList = new ArrayList<>();
  for (int i = 0; i < 10; i++) {
      Object obj = new Object();
      objectList.add(obj);
      obj = null;
   }

在這個(gè)例子中,循環(huán)多次將 new 出來(lái)的對(duì)象放入一個(gè)靜態(tài)的集合中,因?yàn)殪o態(tài)變量的生命周期和應(yīng)用程序一致,而且他們所引用的對(duì)象 Object 也不能釋放,這樣便造成了內(nèi)存泄露。

解決方法:在集合元素使用之后從集合中刪除,等所有元素都使用完之后,將集合置空。

   objectList.clear();
   objectList = null;

4、其他的情況

除了上述 3 種常見(jiàn)情況外,還有其他的一些情況

  • 1、需要手動(dòng)關(guān)閉的對(duì)象沒(méi)有關(guān)閉

  • 網(wǎng)絡(luò)、文件等流忘記關(guān)閉

  • 手動(dòng)注冊(cè)廣播時(shí),退出時(shí)忘記 unregisterReceiver()

  • Service 執(zhí)行完后忘記 stopSelf()

  • EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)

  • 2、static 關(guān)鍵字修飾的成員變量

  • 3、ListView 的 Item 泄露

三、利用工具進(jìn)行內(nèi)存泄露的排查

除了必須了解常見(jiàn)的內(nèi)存泄露場(chǎng)景以及相應(yīng)的解決方法之外,掌握一些好用的工具,能讓我們更有效率地解決內(nèi)存泄露的問(wèn)題。

1、Android Lint

Lint 是 Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發(fā)現(xiàn)代碼機(jī)構(gòu) / 質(zhì)量問(wèn)題,同時(shí)提供一些解決方案,檢測(cè)內(nèi)存泄露當(dāng)然也不在話下,使用也是非常的簡(jiǎn)單,可以參考下這篇文章:Android 性能優(yōu)化:使用 Lint 優(yōu)化代碼、去除多余資源

2、leakcanary

LeakCanary 是 Square 公司開(kāi)源的「Android 和 Java 的內(nèi)存泄漏檢測(cè)庫(kù)」,Square 出品,必屬精品,功能很強(qiáng)大,使用也很簡(jiǎn)單。建議直接看 Github 上的說(shuō)明:leakcanary,也可以參考這篇文章:Android內(nèi)存優(yōu)化(六)LeakCanary使用詳解


參考資料

  • Android 內(nèi)存泄露分析

  • Android 性能優(yōu)化:手把手帶你全面了解內(nèi)存泄露

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

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Android中內(nèi)存泄漏與OOM避免措施總結(jié)
容易導(dǎo)致內(nèi)存泄露的場(chǎng)景
android.view.WindowManager$BadTokenException: Unable to add window
Android性能優(yōu)化之常見(jiàn)的內(nèi)存泄漏
Android內(nèi)存優(yōu)化
Android內(nèi)存性能優(yōu)化
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

主站蜘蛛池模板: 依安县| 巨野县| 昌图县| 丰县| 平度市| 会昌县| 通化市| 临夏县| 富蕴县| 余干县| 巴东县| 泸州市| 竹北市| 乐亭县| 鄄城县| 武邑县| 白城市| 阿城市| 台中市| 专栏| 浦东新区| 封丘县| 晋江市| 克东县| 仲巴县| 思茅市| 长沙县| 万全县| 柯坪县| 石屏县| 新邵县| 衢州市| 清苑县| 日喀则市| 大邑县| 昌乐县| 通城县| 临海市| 九寨沟县| 南乐县| 松潘县|