1.單例導致
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長,所以如果使用不恰當?shù)脑挘苋菀自斐蓛?nèi)存泄漏。比如下面一個典型的例子:
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;
}
}
如果此時傳入的是 Activity 的 Context,當這個 Context 所對應(yīng)的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應(yīng)用程序的生命周期,所以當前 Activity 退出時它的內(nèi)存并不會被回收,這就造成泄漏了,可以換成以下寫法
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
有時候在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復創(chuàng)建,不過這種寫法卻會造成內(nèi)存泄漏,因為非靜態(tài)內(nèi)部類默認會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實例,該實例的生命周期和應(yīng)用的一樣長,這就導致了該靜態(tài)實例一直會持有該Activity的引用,導致Activity的內(nèi)存資源不能正?;厥?/p>
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
正確的做法應(yīng)該是
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。
2 非靜態(tài)內(nèi)部類導致
原因
1非靜態(tài)內(nèi)部類對外部類會存在一個隱式引用
class Out extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
launch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
outmethod();
}
});}
void outmethod(){
}
}
內(nèi)部類能調(diào)用外部類的方法是因為持有外部類的引用
2非靜態(tài)內(nèi)部類中存在異步任務(wù),可能會導致其對應(yīng)的外部類內(nèi)存資源無法正常釋放
class Out extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
launch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.getData();
}
});}
void outmethod(){
}
}
這里面有網(wǎng)絡(luò)請求的耗時任務(wù)
解決方案:
解決思路
1去除隱式引用(通過靜態(tài)內(nèi)部類來去除隱式引用)
2手動管理對象引用(修改靜態(tài)內(nèi)部類的構(gòu)造方式,手動引入其外部類引用)
3當內(nèi)存不可用時,不執(zhí)行不可控代碼(Android可以結(jié)合智能指針,WeakReference包裹外部類實例)
class Out extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
launch.setOnClickListener(new listener(Out.this));
}
void outmethod(){}
static class listener implements View.OnClickListener{
private WeakReference<Out> weakReference;
public listener(Out out) {
this.weakReference=new WeakReference<Out>(out);
}
@Override
public void onClick(View view) {
weakReference.get().outmethod();
}
}
}
android中的幾類引用
StrongReference強引用可以直接訪問目標對象,強引用所關(guān)聯(lián)的對象,在任何時候都不會被內(nèi)存回收,JVM寧肯拋出OOM異常,也不會對其進行回收,所以,在通常的內(nèi)存泄漏中,大多都有強引用的身影
(SoftReference)
軟引用是除了硬引用之外最強的一種引用,軟引用和硬引用的不同點在于,軟引用是可被回收的;回收機制是:當內(nèi)存充足的時候,在GC時,不會去回收當前的軟引用,當內(nèi)存臨近閾值或不足的時候,在GC時,發(fā)現(xiàn)某一對象的引用只具有軟引用當前軟引用就會被回收。
(WeakReference)
弱引用是比軟引用和硬引用更弱的一種引用,在GC時,不論內(nèi)存是否充足,發(fā)現(xiàn)某一對象的引用只具有弱引用當前弱引用就會被回收。
(PhantomReference)
虛引用不能保證其保存對象生命周期,其保存對象若只有虛引用,則其有效期完全隨機于GC的回收,在任何一個不確定的時間內(nèi),都可能會被回收;而虛引用與其他幾者的引用不同在于,在使用PhantomReference,必須要和Reference聯(lián)合使用。
引用使用方式
ReferenceQueue queue = new ReferenceQueue();
WeakReference weakReference = new WeakReference(this,queue);
SoftReference softReference = new SoftReference(this,queue);
PhantomReference phantomReference = new PhantomReference(this,queue);
那么這個ReferenceQueue有什么用呢?
引用對象本身,也是一個強引用,其除了具有保存一個對象本身特有的引用屬性之外,引用對象本身也具有java對象的一般性,那么在其本身保存的對象被回收之后,引用對象本身也就沒有了實用性質(zhì),需要一個適當?shù)那謇頇C制,來清理這些對象,避免大量這些引用對象而帶來的內(nèi)存泄漏;這時候,就可以用到ReferenceQueue。
當引用(SoftReference/WeakReference/PhantomReference)中保存的的對象,被GC回收時,引用本身的這個對象會被加入到ReferenceQueue中,那么,也就是說,ReferenceQueue中保存的對象是Reference,并且是失去了其保存的對象的Reference。這個時候我們可以通過調(diào)用ReferenceQueue中提供的poll()這個API來獲取隊列中的對象,當隊列中不存在對象的時候,返回的會是null,當存在或存在多個的時候,都是返回最前面的一個Reference對象,這個時候我們就需要將這個對象進行清除,讓相應(yīng)的內(nèi)存可以被釋放掉。
Reference ref = null;
while ((ref = queue.poll()) != null) {
// 清除ref
}
3靜態(tài)內(nèi)部類中創(chuàng)建了一個靜態(tài)實例,會導致內(nèi)存泄漏(參考上面單例導致的內(nèi)存泄漏)
3.資源回收
對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷,否則這些資源將不會被回收,從而造成內(nèi)存泄漏。
4ListView
初始時ListView會從BaseAdapter中根據(jù)當前的屏幕布局實例化一定數(shù)量的View對象,同時ListView會將這些View對象緩存起來。當向上滾動ListView時,原先位于最上面的Item的View對象會被回收,然后被用來構(gòu)造新出現(xiàn)在下面的Item。這個構(gòu)造過程就是由getView()方法完成的,getView()的第二個形參convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。構(gòu)造Adapter時,沒有使用緩存的convertView。
5webview
我們不要使用WebView對象時,應(yīng)該調(diào)用它的destory()函數(shù)來銷毀它,并釋放其占用的內(nèi)存,否則其長期占用的內(nèi)存也不能被回收,從而造成內(nèi)存泄露。
6集合
我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。