什么是內(nèi)存泄漏踩寇?
Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒有使用價(jià)值了(可以說生命周期結(jié)束了),但是它們卻可以直接或間接地引用到gc root導(dǎo)致無法被GC回收藏古。嚴(yán)重時(shí)會(huì)造成內(nèi)存溢出OOM。
什么是內(nèi)存溢出忍燥?
Android系統(tǒng)為每一個(gè)應(yīng)用程序都設(shè)置了一個(gè)硬性的條件:DalvikHeapSize最大閥值64M/48M/24M.如果你的應(yīng)用程序內(nèi)存占用接近這個(gè)閥值校翔,此時(shí)如果再嘗試內(nèi)存分配的時(shí)候就會(huì)造成OOM。
內(nèi)存泄露的例子分析
一灾前、 靜態(tài)變量引起的內(nèi)存泄漏
在java中靜態(tài)變量的生命周期是在類加載時(shí)開始防症,類卸載時(shí)結(jié)束。換句話說哎甲,在android中其生命周期是在進(jìn)程啟動(dòng)時(shí)開始蔫敲,進(jìn)程死亡時(shí)結(jié)束。所以在程序的運(yùn)行期間炭玫,如果進(jìn)程沒有被殺死奈嘿,靜態(tài)變量就會(huì)一直存在,不會(huì)被回收掉吞加。如果靜態(tài)變量強(qiáng)引用了某個(gè)Activity中變量裙犹,那么這個(gè)Activity就同樣也不會(huì)被釋放,即便是該Activity執(zhí)行了onDestroy(不要將執(zhí)行onDestroy和被回收劃等號(hào))尽狠。這類問題的解決方案為:1.尋找與該靜態(tài)變量生命周期差不多的替代對(duì)象。2.若找不到叶圃,將強(qiáng)引用方式改成弱引用袄膏。比較典型的例子如下:
1. 單例引起的Context內(nèi)存泄漏
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
當(dāng)調(diào)用getInstance時(shí),如果傳入的context是Activity的context掺冠。只要這個(gè)單例沒有被釋放沉馆,這個(gè)Activity也不會(huì)被釋放。
解決方案
傳入Application的context,因?yàn)锳pplication的context的生命周期比Activity長(zhǎng)德崭,可以理解為Application的context與單例的生命周期一樣長(zhǎng)斥黑,傳入它是最合適的。(有一種說法:內(nèi)存泄漏就是生命周期不一致造成的)
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
//將傳入的context轉(zhuǎn)換成Application的context
mInstance = new IMManager(context.getApplicationContext());
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
二眉厨、 非靜態(tài)內(nèi)部類引起的內(nèi)存泄漏
內(nèi)部類的優(yōu)勢(shì)之一就是可以訪問外部類锌奴,不幸的是,導(dǎo)致內(nèi)存泄漏的原因憾股,就是內(nèi)部類持有外部類實(shí)例的強(qiáng)引用缨叫。
在java里,非靜態(tài)內(nèi)部類 和 匿名類 都會(huì)潛在的引用它們所屬的外部類荔燎。但是耻姥,靜態(tài)內(nèi)部類卻不會(huì)。如果這個(gè)非靜態(tài)內(nèi)部類實(shí)例做了一些耗時(shí)的操作有咨,就會(huì)造成外圍對(duì)象不會(huì)被回收琐簇,從而導(dǎo)致內(nèi)存泄漏。這類問題的解決方案為:1.將內(nèi)部類變成靜態(tài)內(nèi)部類 2.如果有強(qiáng)引用Activity中的屬性座享,則將該屬性的引用方式改為弱引用婉商。3.在業(yè)務(wù)允許的情況下,當(dāng)Activity執(zhí)行onDestory時(shí)渣叛,結(jié)束這些耗時(shí)任務(wù)丈秩。
1. 內(nèi)部線程造成的內(nèi)存泄漏
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
public void test() {
//匿名內(nèi)部類會(huì)引用其外部實(shí)例LeakAty.this,所以會(huì)導(dǎo)致內(nèi)存泄漏,默認(rèn)持有外部Activity實(shí)例
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
解決方案
將非靜態(tài)匿名內(nèi)部類修改為靜態(tài)匿名內(nèi)部類
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
//加上static淳衙,變成靜態(tài)匿名內(nèi)部類 (靜態(tài)方法不能引用非靜態(tài)方法)
public static void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
2. Handler引起的內(nèi)存泄漏
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新數(shù)據(jù)
break;
default:
break;
}
};
};
private void fetchData() {
//獲取數(shù)據(jù)
mHandler.sendEmptyMessage(0);
}
}
mHandler 為匿名內(nèi)部類實(shí)例蘑秽,會(huì)引用外圍對(duì)象LeakAty.this,如果該Handler在Activity退出時(shí)依然還有消息需要處理,那么這個(gè)Activity就不會(huì)被回收箫攀。
比如尤其是:mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
解決方案
public class LeakAty extends Activity {
private TextView tvResult;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
tvResult = (TextView) findViewById(R.id.tvResult);
handler = new MyHandler(this);
fetchData();
}
//第一步肠牲,將Handler改成靜態(tài)內(nèi)部類。
private static class MyHandler extends Handler {
//第二步靴跛,將需要引用Activity的地方缀雳,改成弱引用。
private WeakReference<LeakAty> atyInstance;
public MyHandler(LeakAty aty) {
this.atyInstance = new WeakReference<LeakAty>(aty);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakAty aty = atyInstance == null ? null : atyInstance.get();
//如果Activity被釋放回收了梢睛,則不處理這些消息
if (aty == null || aty.isFinishing()) {
return;
}
aty.tvResult.setText("fetch data success");
}
}
private void fetchData() {
// 獲取數(shù)據(jù)
handler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
//第三步肥印,在Activity退出的時(shí)候移除回調(diào)
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
小結(jié)
雖然靜態(tài)類與非靜態(tài)類之間的區(qū)別并不大识椰,但是對(duì)于Android開發(fā)者而言卻是必須理解的。至少我們要清楚深碱,如果一個(gè)內(nèi)部類實(shí)例的生命周期比Activity更長(zhǎng)腹鹉,那么我們千萬不要使用非靜態(tài)的內(nèi)部類。最好的做法是莹痢,使用靜態(tài)內(nèi)部類,然后在該類里使用弱引用來指向所在的Activity墓赴。
三竞膳、 資源未關(guān)閉引起的內(nèi)存泄漏
當(dāng)使用了BraodcastReceiver、Cursor诫硕、Bitmap坦辟、自定義屬性attr等資源時(shí),當(dāng)不需要使用時(shí)章办,需要及時(shí)釋放掉锉走,若沒有釋放,則會(huì)引起內(nèi)存泄漏
四藕届、 不用的監(jiān)聽未移除
調(diào)用了View.getViewTreeObserver().addOnXXXListener ,而沒有調(diào)用View.getViewTreeObserver().removeXXXListener
五挪蹭、 無限循環(huán)動(dòng)畫
在Activity中播放屬性動(dòng)畫中的一類無限循環(huán)動(dòng)畫,沒有在onDestory中停止動(dòng)畫休偶,Activity會(huì)被動(dòng)畫持有而無法釋放梁厉。
如何避免內(nèi)存泄露:
1) 減小對(duì)象的內(nèi)存占用:
a) 使用更加輕量級(jí)的數(shù)據(jù)結(jié)構(gòu):
考慮適當(dāng)?shù)那闆r下替代HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)而使用安卓專門為手機(jī)研發(fā)的數(shù)據(jù)結(jié)構(gòu)類ArrayMap/SparseArray。SparseLongMap/SparseIntMap/SparseBoolMap更加高效踏兜。
HashMap.put(string,Object);Object o = map.get(string);會(huì)導(dǎo)致一些沒必要的自動(dòng)裝箱和拆箱词顾。
b) 適當(dāng)?shù)谋苊庠赼ndroid中使用Enum枚舉,替代使用普通的static常量碱妆。(一般還是提倡多用枚舉---軟件的架構(gòu)設(shè)計(jì)方面肉盹;如果碰到這個(gè)枚舉需要大量使用的時(shí)候就應(yīng)該更加傾向于解決性能問題。)疹尾。
c) 較少Bitmap對(duì)象的內(nèi)存占用上忍。
使用inSampleSize:計(jì)算圖片壓縮比例進(jìn)行圖片壓縮,可以避免大圖加載造成OOM; decodeformat:圖片的解碼格式選擇纳本,ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,還可以使用WebP睡雇。
d) 使用更小的圖片
資源圖片里面,是否存在還可以繼續(xù)壓縮的空間饮醇。
2) 內(nèi)存對(duì)象的重復(fù)利用:
使用對(duì)象池技術(shù)它抱,兩種:1.自己寫;2.利用系統(tǒng)既有的對(duì)象池機(jī)制朴艰。比如LRU(Last Recently Use)算法观蓄。
a) ListView/GridView源碼可以看到重用的情況ConvertView的復(fù)用混移。RecyclerView中Recycler源碼。
b) Bitmap的復(fù)用
Listview等要顯示大量圖片侮穿。需要使用LRU緩存機(jī)制來復(fù)用圖片歌径。
C) 避免在onDraw方法里面執(zhí)行對(duì)象的創(chuàng)建,要復(fù)用亲茅。避免內(nèi)存抖動(dòng)回铛。
D) 常見的java基礎(chǔ)問題---StringBuilder等