<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] # 內存泄漏 本文對網絡上的內存泄漏相關知識進行分析、學習,并對一些知識點提出了自己的見解,包括什么是內存泄漏、可能出現內存泄漏的情況以及如何避免內存泄漏。 ## 什么是內存泄漏 存在下面的這種對象,這些對象不會被 GC 回收,卻占用著內存,即為內存泄漏(簡單說:存在已申請的無用內存無法被回收) * 該對象是可達的,即還在被引用著 * 該對象是無用的,即程序以后不會再使用該對象 ## 可能出現內存泄漏的情況 長生命周期的對象持有短生命周期的引用,就很可能會出現內存泄露。 ![](https://img.kancloud.cn/6d/2c/6d2c2189caf6773382c1642b5b3a4280_3056x1296.png) 對于網上內存泄漏情況分析的文章,我個人持有一些不同的觀點,下面按情況來分析: ### 靜態集合未及時刪除無用對象 網上比較多的文章在這個例子下都是拿 Vector 作為例子,Vector 其實是一個已經過時、廢棄的類了,下面以最常見的 ArrayList 來分析: ```java static List<Object> sObjectList = new ArrayList<>(); for (int i = 0; i < 100; i++) { Object o = new Object(); sObjectList.add(o); } ``` 當某些對象無用,我們需要將其手動從集合中移除時,此時會刪除集合對對象的引用,避免內存泄漏發送。這一點我們可以從 JDK 的 ArrayList 源碼中看到: ```java public E remove(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } ``` 可以看到在倒數第三行將集合內部對象引用置為 null,此時該對象內存可得到回收。如果不及時將無用對象從集合中移除,由于靜態類的生命周期和應用的周期一樣長,所有集合元素對象會一直保持強引用狀態,易造成內存泄漏。 ### HashSet存儲對象的屬性被修改,調用HashSet的remove方法失效 小提示:實現 Set 接口的集合元素為無序、不可重復的 網上對這種類型的內存泄漏描述為: > 當集合里面的對象屬性被修改后,再調用 remove() 方法時不起作用 我認為應該描述為 HashSet 才準確,因為其他集合并不存在這種問題。要了解在 HashSet 中為什么不起作用,我們需要先看看 HashSet 的實現方式。HashSet 的存儲實現其實是依賴于 HashMap,在調用 HashSet 的 add(Object o)方法時,實際上是將對象 o 作為 key,虛擬了一個對象作為 value,將這個 Entry 存入 HashSet 所持有的一個 HashMap 中。源碼如下: ```java // ... // HashSet 維護的 HashMap private transient HashMap<E,Object> map; // 與 HashMap Entry相關連的虛擬的值 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; } // ... ``` 所以就可以理解為什么網上都是以下面代碼作為這種內存泄漏的例子了。 ```java HashSet<Student> hashSet = new HashSet<>(); Student xiaoming = new Student("xiaoming", 15); Student xiaogang = new Student("xiaogang", 14); hashSet.add(xiaoming); hashSet.add(xiaogang); xiaoming.setAge(16); // 此時 xiaoming 對象的 hashCode 值發生改變 hashSet.remove(xiaoming); hashSet.add(xiaoming); ``` 對照代碼看: * 第 6 行:由于更改了對象的屬性之后,其 hashCode 會變化 * 第 7 行:從 HashSet 中移除元素時其實就是從 HashSet 持有的那個 HashMap 中移除,在從 HashMap 中根據 key 移除條目時,會計算 key 的 hashCode,而這個 key 其實就是 HashSet 中的元素對象 xiaoming,它的 hashCode 已經改變了 * 第 8 行:所以會移除失敗,并且可以再次添加 xiaoming 對象,因為它的 hashCode 變了 然后 HashMap 根據計算得到的 hash 值,再從存儲結構中尋找指定的條目并移除,具體實現就不在本文研究范圍之內了。 ### 非靜態內部類持有外部類引用 且 非靜態內部類對象生命周期大于外部類對象生命周期 #### 監聽器 關于監聽器導致的內存泄漏,網上描述如下: > 在 java 編程中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會調用一個控件的諸如 addXXXListener() 等方法來增加監聽器,但往往在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增加了內存泄漏的機會。 其實描述并無問題,但沒說明為什么不刪除監聽器就會導致內存泄漏。 其實不光監聽器,Java 所有的非靜態內部類都有可能造成內存泄漏,當然監聽器屬于匿名內部類,也算非靜態內部類的一種。 非靜態內部類包括成員內部類、局部內部類和匿名內部類。在編譯器進行編譯時,會自動為非靜態內部類添加一個指向外部類的成員變量,所以非靜態內部類持有外部類的引用。下面舉兩個 Android 開發中常見的例子: #### 多線程 ```java new Thread(new Runnable() { @Override public void run() { try { // 模擬耗時操作 Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); ``` 在外部類退出時,耗時操作仍未完成的情況下,子線程仍會持有外部類的引用,導致外部類占用空間無法回收,造成內存泄漏。 #### Handler ```java public class MainActivity extends AppCompatActivity { private Handler mHandler = new MyHandler(); // 此處 MyHandler 為成員內部類 class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); // do something } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler.postDelayed(new Runnable() { @Override public void run() { // do something } }, 1000 * 60 * 10); } } ``` 上面例子中,Handler為成員內部類,持有外部類Activity對象的引用。Activity退出后,Handler消息延遲10分鐘發出,Activity引用無法釋放,造成內存泄漏。解決方案為:a、將Handler改為靜態內部類;b、在Activity退出時清空消息隊列。 ### 連接未及時釋放 及時關閉各種數據庫連接、Socket連接、IO連接等 ### 單例模式使用生命周期短的Context ```java public class Singleton { private static volatile Singleton sInstance; private Context mContext; public static synchronized Singleton getInstance(Context context) { if (sInstance == null) { synchronized (Singleton.class) { if (sInstance == null) { sInstance = new Singleton(context); } } } return sInstance; } private Singleton(Context context) { mContext = context; } } ``` 在上面這段代碼中可以看到,單例模式的 getInstance 方法需要一個 Context 參數,這個參數很關鍵。因為單例對象的生命周期和應用的生命周期一樣長,如果在首次調用 getInstance 方法時傳入的 Context 是 Activity,那么單例對象會持有該 Activity 的引用,當該 Activity 生命周期結束時,Activity 依然被生命周期更長的單例對象引用,GC 無法回收其內存導致內存泄漏。 ## 避免內存泄漏的方法 ### 將非靜態內部類改為靜態內部類 2017 年 10 月 21 日更新:下面這種寫法是有問題的,會造成內存泄漏。 ```java private static class MyRunnable implements Runnable { WeakReference<MainActivity> mWeakReference; public MyRunnable(MainActivity mainActivity) { mWeakReference = new WeakReference<>(mainActivity); } @Override public void run() { MainActivity mainActivity = mWeakReference.get(); // get 方法會獲得強引用 if (mainActivity != null) { // 模擬耗時操作 try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } mainActivity.mStudent.setAge(12); } } } ``` 此時 MyRunnable 成為靜態內部類,不再持有外部類的引用。但同時出現一個問題,MyRunnable 內部無法使用外部類的非靜態成員變量了,此時可以讓 MyRunnable 類持有外部類的弱引用,通過外部類的弱引用調用其非靜態成員變量。 最近項目中的代碼改成這樣子的寫法,但是在實際測試過程中發現,依舊會造成內存泄漏。分析結果如下: 當在調用 new Thread(new MyRunnable(MainActivity.this)).start 時,MyRunnable 持有了 MainActivity 的引用,為弱引用。但是在執行到 run 方法時,mainActivity 通過 mWeakReference.get 方法獲取到了 MainActivity 的實例對象,為強引用。如果在線程耗時操作過程中退出 MainActivity,此時子線程依舊會持有 MainActivity 的強引用會造成內存泄漏。 對于這種情況,我暫時采取了添加標識位,在 Activity 退出時,結束子線程。[結束子線程的方法參考鏈接](http://www.jianshu.com/p/536b0df1fd55) 而對于 Handler,則可以使用上述解決方案,如下: ```java private static class MyHandler extends Handler { private WeakReference<MainActivity> mWeakReference; MyHandler(MainActivity MainActivity) { mWeakReference = new WeakReference<>(MainActivity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity activity = mWeakReference.get(); if (activity != null) { switch (msg.what) { case FINISH_SWIPE_CARD: activity.mScanBar.setVisibility(View.GONE); break; default: break; } } } } ``` ### 通過程序邏輯切段引用 由于內部類持有外部類的引用導致內存泄漏,那么我們就把這條路切斷,就可以避免內存泄漏。 * 在外部類退出的時候,結束掉后臺子線程,引用就被切斷 * 如果是 handler 時,要判斷到底是誰持有了 handler 引用。如果是后臺線程,那么就結束掉后臺線程;如果是 delay 的 message 持有,那么可以清除掉消息隊列所有消息,切斷 Looper 消息隊列對 handler 的引用: ```java @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } ``` ### 單例模式使用生命周期更長的 Context 在單例模式引起的內存泄漏中,為了避免長生命周期的單例引用短生命周期的 Activity,我們應當使用生命周期更長的 ApplicationContext: ```java public class Singleton { private static volatile Singleton sInstance; private Context mContext; public static synchronized Singleton getInstance(Context context) { if (sInstance == null) { synchronized (Singleton.class) { if (sInstance == null) { sInstance = new Singleton(context); } } } return sInstance; } private Singleton(Context context) { mContext = context.getApplicationContext(); } } ``` 我們在項目中也應當優先使用 ApplicationContext,它可以適用于大部分情況,除了 startActivity 和 show dialog 之外: ![](https://ws1.sinaimg.cn/large/006tKfTcgy1fkuqqx5y4xj30qi09et8u.jpg) ## 總結 其實,歸結于一句話,避免內存泄漏,主要還是: 避免長生命周期對象持有短生命周期對象的引用。 參考文檔: [Android 中使用 Handler 造成內存泄露的分析和解決-扔物線](https://my.oschina.net/rengwuxian/blog/181449) [Android 中 Handler 引起的內存泄露-技術小黑屋](http://droidyue.com/blog/2014/12/28/in-android-handler-classes-should-be-static-or-leaks-might-occur/index.html) [android-內部類導致的內存泄漏實戰解析](http://blog.csdn.net/sinat_31057219/article/details/74533647) [內存泄露淺析](http://afayp.me/2016/09/23/%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E6%B5%85%E6%9E%90/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看