Android 內存管理

JoeannL47 8年前發布 | 6K 次閱讀 安卓開發 Android開發 移動開發

1. Android中的內存

1.1 Android中的垃圾回收機制

Android 平臺最吸引開發者的一個特性:有垃圾回收機制,無需手動管理內存,Android 系統會自動跟蹤所有的對象,并釋放那些不再被使用的對象

  • Young Generation 新生代

    1. 大多數新建對象都位于Eden(伊甸園)區
    2. 當Eden區被對象填滿時,就會執行Minor GC(輕量GC)。并把所有存活下來的對象轉移到其中一個survivor區
    3. Survivor Space:S0、S1有兩個,存放每次垃圾回收后存活的對象
    4. Minor GC 同樣會檢查survivor區中存活下來的對象,并把他們轉移到另一個survivor區,這樣在一段時間內,總會有一個空的survivor區

  • Old Generation 老生代

    1. 存放長期存活的對象和經過多次Minor GC后依然存活下來的對象
    2. 滿了進行Major GC(較重GC)

  • Permanent Generation 永久代

  • 存放方法區,方法區中有,要加載的類信息、靜態變量、final類型的常量、屬性和方法信息

1.2 垃圾回收

  • 內存占用過多,需要為新對象分配空間
  • 不同的虛擬機發生GC時采用的策略不同,可能會暫停當前程序的執行

1.3 垃圾回收機制&FPS

  • Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,那么整個過程如果保證在16ms以內就能達到一個流暢的畫面。60FPS
  • 如果某一幀的操作超過了16ms就會讓用戶感覺到卡頓
  • UI渲染過程發生GC,導致某一幀繪制時間超過16ms

1.4 內存泄漏

在整個Android開發過程中,內存泄漏是導致OOM(Out Of Memory內存溢出)的一個重要因素

  1. 應用程序分配了大量不能回收的對象
  2. 這導致可分配的內存越來越少
  3. 當新對象的創建需要的內存不夠
  4. 當發現內存不夠就會調用一次GC進行垃圾回收
  5. 結果:就會發生卡頓

1.5 內存抖動

原因:內存抖動是因為應用程序在短時間內創建大量的對象,又被馬上釋放。

  1. 瞬間產生大量的對象會嚴重占用Young Generation的內存區域
  2. 當達到閾值,剩余空間不夠,就會觸發GC從而導致剛產生的對象又很快被回收。
  3. 即時每次分配的對象占用了很少的內存,頻分GC疊加在一起會增加Heap的壓力
  4. 從而觸發更多其他類型的GC。
  5. 結果:這個操作有可能會影響到幀率,并使用戶感知到性能問題

2. 內存檢測工具

2.1 Memory Monitor 內存監視器

  • 優點
    • 方便顯示內存使用和GC情況
    • 快速定位卡頓是否和GC有關
    • 快速定位Crash崩潰是否和內存占用過高有關
    • 快速定位潛在的內存泄漏問題
    • 簡單易用
    </li>
  • 缺點
    • 不能準確定位問題
    • </ul> </li> </ul>

      2.2 Allocation Tracker 分配跟蹤器

      • 優點
        • 定位代碼中分配對象的類型,大小,時間,線程,堆棧等信息
        • 定位內存抖動問題
        • 配合HeapViewer一起定位內存泄漏問題
        </li>
      • 缺點
        • 使用復雜
        • </ul> </li>
        • 顯示所有對象的信息(環形圖)
        • </ul>

          2.3 Heap Viewer 堆視圖

          • 優點
            • 內存快照信息
            • 每次GC之后收集一次信息
            • 查找內存泄漏利器
            </li>
          • 缺點
            • 使用復雜
            • </ul> </li>
            • 顯示已分配的對象大小信息(包視圖)
            • </ul>

              2.4 Leak Canary

              • 引用

                dependencies {
                      debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
                      releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
                  }
              • 使用

                // 內存泄漏檢測
                  private RefWatcher refWatcher;
                  public static RefWatcher getRefWatcher(Context context) {
                      TTApplication application = (TTApplication) context.getApplicationContext();
                      return application.refWatcher;
                  }
                  @Override
                  public void onCreate() {
                      super.onCreate();
                      refWatcher = LeakCanary.install(this);        // 檢測
                  }

                public void onDestroy() { RefWatcher refWatcher = TTApplication.getRefWatcher(getActivity()); refWatcher.watch(this); //內存泄露檢測 }</code></code></pre> </li> </ul>

                3. 常見的內存泄漏問題

                3.1 單例造成的泄漏

                將Context對象保存在單例模式中,instance對象本身持有一個Context對象的引用,活動即時被銷毀也不能被回收,因為靜態變量一直持有它的引用

                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;
                    }
                }

                可以改為

                public class AppManager {
                    private static AppManager instance;
                    private Context context;
                    private AppManager(Context context) {

                    //    使用Application的Context(也可以用自定義的Application)
                    this.context = context.getApplicationContext();        
                }
                public static AppManager getInstance(Context context) {
                    if (instance != null) {
                        instance = new AppManager(context);
                    }
                    return instance;
                }
                

                }</code></pre>

                3.2 非靜態內部類的靜態實例造成的泄漏

                靜態的sResource在創建時會間接持有一個MainActivity實例的引用,導致MainActivity無法被回收

                public class MainActivity extends Activity {
                    private static TestResource sResource = null;
                    @Override
                    protected void onCreate(Bundle savedInstanceState) {
                        super.onCreate(savedInstanceState);
                        setContentView(R.layout.activity_main);

                    if (sResource == null) {
                        sResource = new TestResource();
                    }
                    // ...
                }
                
                // 非靜態內部類
                class TestResource {
                    // ...
                }
                

                }</code></pre>

                將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例

                如果用到Context就使用Application的Context

                但是Dialog不能使用Application和Service的Context

                3.3 Handler 造成的內存泄漏問題

                當創建匿名對象時,該對象會間接持有外部類實例的一個引用,mHandler對象本身會持有MainActivity的引用,導致MainActivity銷毀后無法即時被回收

                public class MainActivity extends Activity {
                    private Handler mHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                        }
                    };

                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_main);
                    loadData();
                }
                
                private void loadData() {
                    // ...request
                    Message message = Message.obtain();
                    mHandler.sendMessage(message);
                }
                

                }</code></pre>

                在Activity中避免使用非靜態內部類,比如將Handler聲明為靜態的,這樣Handler的存活時間就與Activity無關了

                同時引入弱引用的方式引入Activity,避免將Activity作為Context傳入

                使用前判空

                public class MainActivity extends Activity {
                    private static class MyHandler extends Handler {
                        private final WeakReference<MainActivity> mActivity;

                    private MyHandler(MainActivity activity){
                        mActivity = new WeakReference<MainActivity>(activity);
                    }
                
                    @Override
                    public void handleMessage(Message msg) {
                    }
                }
                
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_main);
                    loadData();
                }
                
                private void loadData() {
                    // ...request
                    Message message = Message.obtain();
                    mHandler.sendMessage(message);
                }
                

                }</code></pre>

                3.4 集合類泄漏

                • 如果集合類是全局的變量(類中的靜態屬性,全局性的map等既有靜態引用或final一直指向它)
                • 沒有相應的刪除機制
                • 很可能導致集合所占用的內存只增不減

                4. 避免內存泄漏的方法

                1. 盡量不要讓靜態變量引用Activity
                2. 使用WeakReference弱引用,會保證GC時會被回收
                3. 使用靜態內部類來代替內部類,靜態內部類不持有外部類的引用
                4. 靜態內部類使用弱引用來引用外部類
                5. 在聲明周期結束的時候釋放資源

                5. 減少內存使用

                1. 使用更輕量的數據結構(SpareArray代替HashMap)

                  • Google自己定義的類占用內存更小
                2. 避免在onDraw方法中創建對象

                  • onDraw()方法被頻繁調用,在其中創建對象會導致臨時對象過多,發生內存抖動
                3. 對象池(Message.obtain())

                  • 當一定要在onDraw中創建對象,推薦使用對象池
                  • 相當于對象緩沖,在創建時查找是否已經存在對象,沒有在創建
                4. LRUCache

                  • 大大減少內存使用
                5. Bitmap內存復用,壓縮(inSampleSize,inBitmap)

                6. StringBuilder

                  • 代替String,尤其是進行拼接操作時

                 

                來自:http://www.jianshu.com/p/9fb0a795da93

                 

 本文由用戶 JoeannL47 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!