Android 內存管理
1. Android中的內存
1.1 Android中的垃圾回收機制
Android 平臺最吸引開發者的一個特性:有垃圾回收機制,無需手動管理內存,Android 系統會自動跟蹤所有的對象,并釋放那些不再被使用的對象
-
Young Generation 新生代
- 大多數新建對象都位于Eden(伊甸園)區
- 當Eden區被對象填滿時,就會執行Minor GC(輕量GC)。并把所有存活下來的對象轉移到其中一個survivor區
- Survivor Space:S0、S1有兩個,存放每次垃圾回收后存活的對象
-
Minor GC 同樣會檢查survivor區中存活下來的對象,并把他們轉移到另一個survivor區,這樣在一段時間內,總會有一個空的survivor區
-
Old Generation 老生代
- 存放長期存活的對象和經過多次Minor GC后依然存活下來的對象
-
滿了進行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內存溢出)的一個重要因素
- 應用程序分配了大量不能回收的對象
- 這導致可分配的內存越來越少
- 當新對象的創建需要的內存不夠
- 當發現內存不夠就會調用一次GC進行垃圾回收
- 結果:就會發生卡頓
1.5 內存抖動
原因:內存抖動是因為應用程序在短時間內創建大量的對象,又被馬上釋放。
- 瞬間產生大量的對象會嚴重占用Young Generation的內存區域
- 當達到閾值,剩余空間不夠,就會觸發GC從而導致剛產生的對象又很快被回收。
- 即時每次分配的對象占用了很少的內存,頻分GC疊加在一起會增加Heap的壓力
- 從而觸發更多其他類型的GC。
- 結果:這個操作有可能會影響到幀率,并使用戶感知到性能問題
2. 內存檢測工具
2.1 Memory Monitor 內存監視器
- 優點
- 方便顯示內存使用和GC情況
- 快速定位卡頓是否和GC有關
- 快速定位Crash崩潰是否和內存占用過高有關
- 快速定位潛在的內存泄漏問題
- 簡單易用
- 缺點
- 不能準確定位問題 </ul> </li> </ul>
- 優點
- 定位代碼中分配對象的類型,大小,時間,線程,堆棧等信息
- 定位內存抖動問題
- 配合HeapViewer一起定位內存泄漏問題
- 缺點
- 使用復雜 </ul> </li>
- 顯示所有對象的信息(環形圖) </ul>
- 優點
- 內存快照信息
- 每次GC之后收集一次信息
- 查找內存泄漏利器
- 缺點
- 使用復雜 </ul> </li>
- 顯示已分配的對象大小信息(包視圖) </ul>
-
引用
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. 避免內存泄漏的方法
- 盡量不要讓靜態變量引用Activity
- 使用WeakReference弱引用,會保證GC時會被回收
- 使用靜態內部類來代替內部類,靜態內部類不持有外部類的引用
- 靜態內部類使用弱引用來引用外部類
- 在聲明周期結束的時候釋放資源
5. 減少內存使用
-
使用更輕量的數據結構(SpareArray代替HashMap)
- Google自己定義的類占用內存更小
-
避免在onDraw方法中創建對象
- onDraw()方法被頻繁調用,在其中創建對象會導致臨時對象過多,發生內存抖動
-
對象池(Message.obtain())
- 當一定要在onDraw中創建對象,推薦使用對象池
- 相當于對象緩沖,在創建時查找是否已經存在對象,沒有在創建
-
LRUCache
- 大大減少內存使用
-
Bitmap內存復用,壓縮(inSampleSize,inBitmap)
-
StringBuilder
- 代替String,尤其是進行拼接操作時
來自:http://www.jianshu.com/p/9fb0a795da93
2.4 Leak Canary
2.3 Heap Viewer 堆視圖
2.2 Allocation Tracker 分配跟蹤器