內存泄漏介紹
1.什么是OOM?
OOM(out of memory)即內存溢出.在程序中,對內存使用超過一定的閥值就會導致內存溢出. 而當一個對象已經不需要在使用了,本該被回收,而另一個正在使用的對象持有它的引用,導致該對象不能被回收就產生了內存泄漏.內存泄露太多導致無法繼續(xù)申請內存是導致OOM的主要原因之一.
2.OOM導致的現(xiàn)象?
1.程序卡頓,響應速度慢(內存占用高時JVM虛擬機會頻繁觸發(fā)GC)
2.由于APP運行內存限制,會導致直接崩潰(OutOfMemoryError)
3.觸發(fā)Low Memory Killer機制,應用莫名被殺
3.什么是內存抖動
內存泄漏發(fā)生時的主要表現(xiàn)為內存抖動瓦堵,可用內存慢慢變少,在Android studio中可以通過Android Profiler工具查看內存抖動情況
堆內存都有一定的大小柜某,能容納的數據是有限制的,當Java堆的大小太大時公壤,垃圾收集會啟動停止堆中不再應用的對象栏渺,來釋放內存。當在極短時間內分配給對象和回收對象的過程就是內存抖動澎怒。
內存抖動一般是在循環(huán)語句中創(chuàng)建臨時對象或在繪制時配置大量對象導致泼各。 內存抖動會帶來UI的卡頓,因為大量的對象創(chuàng)建履肃,會很快消耗剩余內存仔沿,導致GC回收,GC會占用大量的幀繪制時間榆浓,從而導致UI卡頓
導致內存泄漏(溢出)的原因有哪些?
1. 創(chuàng)建的資源沒有及時釋放:
如何避免:
資源性對象及時關閉,使用的任何資源都要及時關閉或者異常處理,保證在最惡劣的情況下資源可以得到釋放 (如:Cursor于未、File、Receiver陡鹃、Sensor)
資源的注冊和反注冊成對出現(xiàn)(廣播,觀察者) 如事件注冊后未注銷,會導致觀察者列表中持有Context對象的引用
頁面退出時及時清理一些資源占用(集合對象,WebView) 容器中的對象在不用的時候及時清理,WebView存在著內存泄漏的問題,在應用中只要使用一次,WebView,內存就不會被釋放掉.
資源重復利用(使用adapter的時候使用convertView)
2. 保存了耗用內存過大的對象(Bitmap)
如何避免:
Bitmap沒有使用的時候及時recycle釋放內存
對大圖進行壓縮,使用軟引用或弱引用(使用這兩種引用代碼需要做不為空判斷)
使用緩存技術(LruCache和DiskLruCache)
3.Static引用的資源 消耗過多的實例(Context的使用)
如何避免:
盡量避免static成員變量引用資源 消耗過多的實例,比如Context;靜態(tài)變量不要持有大數據對象
使用軟引用代替強引用**
盡量使用ApplicationContext,因為Application的Context的生命周期比較長,引用它不會出現(xiàn)內存泄露的問題
對于內部類盡量使用靜態(tài)內部類,避免由于內部類導致的內存泄漏(靜態(tài)內部類可以通過軟引用使用外部的Context)如:Handler使用靜態(tài)內部類
4.線程生命周期不可控導致內存泄漏
如何避免:
將線程的內部類,改為靜態(tài)內部類,在線程內部采用弱引用保存Context引用
線程優(yōu)化:避免程序中存在大量的Thread.可以使用線程池,并且頁面退出時,終止線程.**
例:
在activity中handler發(fā)一個延時任務,activity退出后,延遲任務的message還在主線程,它持有activity的handler引用,所以造成內存泄漏(handler非靜態(tài)類,它會持有外部類的引用,也就是activity);
這里可以把handler聲明為static的,則handler的存活和activity生命周期無關了,如果handler內部使用外部類的非static對象(如:Context),應該通過弱引用傳入,activity銷毀時,移除looper線程中的消息.
常見的內存泄漏案例分析
1、單例模式引起的內存泄露
由于單例模式的靜態(tài)特性抖坪,使得它的生命周期和我們的應用一樣長萍鲸,如果讓單例無限制的持有Activity的強引用就會導致內存泄漏
內存泄漏代碼片段:
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context;
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private View mView = null;
public void setXXView(View xxView) {
mView = xxView;
}
}
解決方案:
- 傳入的Context使用ApplicationContext;
- 將該屬性的引用方式改為弱引用;
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context.getApplicationContext();
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private WeakReference<View> mView = null;
public void setXXView(View xxView) {
mView = new WeakReference<View>(xxView);
}
}
2.Handler引發(fā)的內存泄漏
內存泄漏代碼片段:
當Activity退出時,延時任務Message還在主線程的MessageQueue中等待,此時的Message持有Handler的強引用,并且Handler是Activity類的非靜態(tài)內部類,所以也默認持有Activity的強引用.
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
解決方案: 1.使用靜態(tài)內部類,通過弱引用傳入外部類的Context 2.在onDestroy中調用mHandler.removeCallbacksAndMessages(null)
private final Handler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
static class MyHandler extends Handler {
private SoftReference<Activity> reference;
public MyHandler(Activity activity) {
// 持有 Activity 的軟引用
reference = new SoftReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 處理消息
}
}
}
}
4.內部類引起的內存泄漏
內部類默認持有外部類強引用,容易出現(xiàn)內存泄漏
內存泄漏代碼片段:
public void createNonStaticInnerClass(){
CustomThread mCustomThread = new CustomThread();
mCustomThread.start();
}
public class CustomThread extends Thread{
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(5000);
Log.i(TAG,"CustomThread ------- 打印");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
解決方案: 1.把線程類聲明為靜態(tài)的類,如果要用到Activity對象,那么就作為參數傳入且為WeakReference 2.在Activity的onDestroy時,停止線程的執(zhí)行
public static class CustomThread extends Thread{
private WeakReference<MainActivity> mActivity;
public CustomThread(MainActivity activity){
mActivity = new WeakReference<MainActivity>(activity)
}
}
5.Activity Context 的不正確使用引起的內存泄漏
使用ApplicationContext代替Activity Context ,因為ApplicationContext的生命周期也就是該應用生命周期,不依賴于activity的生命周期
內存泄露的工具?
1. MAT工具(很全面,但入手較難,MAT為Eclipse自帶工具)
2. Android Profiler(圖像化工具,AndroidStudio自帶工具)
3. LeakCanary工具(簡便)
源碼:https://github.com/square/leakcanary
支持Eclipse的庫:http://download.csdn.net/detail/ytuglt/9533490
相關鏈接直達:
Android APP性能優(yōu)化之 ---- 布局優(yōu)化(一)
Android APP性能優(yōu)化之 ---- 內存優(yōu)化(二)
Android APP性能優(yōu)化之 ---- 代碼優(yōu)化(三)
Android APP性能優(yōu)化之 ---- 優(yōu)化監(jiān)測工具(四)