簡(jiǎn)介
android 的內(nèi)存泄漏,又稱之為oom。即 android 系統(tǒng)會(huì)為每一個(gè)應(yīng)用分配一定的內(nèi)存空間。而 jvm 也會(huì)在不定時(shí)間間隔進(jìn)行內(nèi)存回收,但是當(dāng)我們的代碼書寫的不夠規(guī)范時(shí)候。導(dǎo)致一些內(nèi)存不能夠回收,累積內(nèi)存超過(guò)應(yīng)用所分配的最大內(nèi)存即會(huì)造成內(nèi)存泄漏。
了解 java 的內(nèi)存
在 java 中內(nèi)存環(huán)境主要分為三種:
靜態(tài)存儲(chǔ)區(qū):存儲(chǔ)靜態(tài)變量以及全局變量數(shù)據(jù)。所有線程共享。
棧:在函數(shù)中的基本變量類型或者對(duì)象類型的引用,java在棧中為其分配空間。當(dāng)函數(shù)結(jié)束時(shí)候,分配的空間進(jìn)行釋放。
堆:堆內(nèi)存用來(lái)存放由new創(chuàng)建的對(duì)象和數(shù)組。 由 java gc 進(jìn)行回收。所有線程進(jìn)行共享。
為什么會(huì)出現(xiàn)內(nèi)存泄漏
1 當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用了,需要被回收時(shí)。另外一個(gè)正在使用的對(duì)象持有了該對(duì)象導(dǎo)致該對(duì)象不能被回收,停留在堆內(nèi)存中。
2 有些對(duì)象只有有限的生命周期,需要被回收。同時(shí)也被一系列的引用。導(dǎo)致不能被回收。
android 中的內(nèi)存泄漏案例:
1 單例造成的內(nèi)存泄漏:
public class SingleDemoInstance {
private static SingleDemoInstance instance;
private Context context;
private SingleDemoInstance(Context context) {
this.context = context;
}
public static SingleDemoInstance getInstance(Context context) {
if (instance == null) {
instance = new SingleDemoInstance(context);
}
return instance;
}
}
上面的單例模式可能造成內(nèi)存泄漏,SingleDemoInstance 的靜態(tài)變量生命周期為應(yīng)用生命周期,假如構(gòu)造單例傳入的 Context 為 Activity 的 Context ,某一時(shí)刻我們想要把 Activity 回收 finish 掉。此時(shí)單例存在,并且持有了 Activity ,那么此時(shí)的 Activity 就不能被回收。可能會(huì)造成內(nèi)存泄漏。
解決辦法:
這里可以用 this.context = context.getApplciationContext() 代替上面 context 的賦值 ; 此時(shí) context 的生命周期為整個(gè)程序運(yùn)行期間,則不會(huì)影響到 Activity 的內(nèi)存釋放。
2 非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏:
class DemoActivity extends Activity {
public static DemoClass demo = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (demo == null) {
demo = new DemoClass();
}
}
private class DemoClass {
}
}
分析上述代碼會(huì)出現(xiàn)內(nèi)存泄漏的情況:
靜態(tài)變量 demo 的生命周期為應(yīng)用周期。
內(nèi)部類 DemoClass 對(duì)外部類 DemoActivity 會(huì)持有引用。
當(dāng) DemoActivity 需要被回收釋放的時(shí)候,發(fā)現(xiàn) DemoClass 的一個(gè)實(shí)體變量對(duì)它進(jìn)行了引用,并且生命周期為應(yīng)用生命周期。故不能成功釋放??赡軐?dǎo)致內(nèi)存泄漏。
解決辦法:
private static class DemoClass {
}
內(nèi)部類改成靜態(tài)的,就不會(huì)持有對(duì)外部類的引用。不會(huì)影響到 DemoActivity 資源的釋放。
3 handler 造成的內(nèi)存泄漏
public class HandlerActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 1000 * 60 * 10);
}
}
在開發(fā)中, handler 經(jīng)常按照上述方法使用,handler 內(nèi)部處理消息可能會(huì)有延遲,上述的方法是為了創(chuàng)造延遲環(huán)境。為什么會(huì)出現(xiàn)內(nèi)存泄漏呢?
handler 的運(yùn)行機(jī)制對(duì) Activity 的持有,假如現(xiàn)在的處理消息有延遲,那么當(dāng) Activity finish 之后想要釋放的時(shí)候,因?yàn)?handler 持有了 Activity ,而 handler 還在等待處理消息, 從而導(dǎo)致了 Activity 不能及時(shí)的釋放。引起內(nèi)存的泄漏。
解決方式 :
(1) 靜態(tài)修飾 handler ,保證 handler 不會(huì)持有 Activity , 不影響 Activity 的資源釋放過(guò)程:
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
(2) 弱引用 Activity ,當(dāng) jvm gc 的時(shí)候能夠回收掉 Activity :
class MyHandler extends Handler {
WeakReference<Activity> mActivity;
public MyHandler(Activity con) {
this.mActivity = new WeakReference<Activity>(con);
}
public void handleMessage(android.os.Message msg) {
if (msg.what == 1) {
Toast.makeText(Activity.this, "toast", 1000).show();
send();
}
};
}
4 線程引起的內(nèi)存泄漏:
public class ThreadActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyThread() {
@Override
public void run() {
SystemClock.sleep(100000);
}
};
}
class MyThread extends Thread {
}
}
這里的線程案例和 handler 的內(nèi)存泄漏原理類似,匿名內(nèi)部類(或者非靜態(tài)的內(nèi)部類)會(huì)引用 Activity ,同時(shí)線程導(dǎo)致的阻塞,導(dǎo)致 Activity 不能及時(shí)釋放資源。
解決方法:
(1) 靜態(tài)修飾 MyThread 類,不再持有 Activity 。
(2) 當(dāng) Activity finish 的時(shí)候 ,對(duì) 線程進(jìn)行 cancel 取消處理 。
5 WebView 引起的內(nèi)存泄漏:
public class WebViewActivity extends Activity {
private WebView mWebView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = findViewById(R.id.web);
mWebView.loadUrl("[http://www.bug.com](http://www.bug.com/)");
}
@Override
protected void onDestroy() {
destoryWebView();
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
private void destoryWebView() {
if (mWebView != null) {
mWebView.pauseTimers();
mWebView.removeAllViews();
mWebView.destroy();
}
}
}
android 的 webview 在加載網(wǎng)頁(yè)的過(guò)程中會(huì)占用大量的堆內(nèi)存,并且占用堆內(nèi)存的空間和加載網(wǎng)頁(yè)的數(shù)量成正比。上述代碼已經(jīng)寫出了解決方案,我們不僅要釋放 webview 的資源,同時(shí)最好把 webview 加載網(wǎng)頁(yè)放置另外一個(gè)進(jìn)程中,當(dāng)加載網(wǎng)頁(yè)的 activity 銷毀的時(shí)候,同時(shí)將該進(jìn)程殺死。避免占用過(guò)多堆內(nèi)存引起的內(nèi)存泄漏問題。
接下來(lái),下篇文章分析 LeakCanary 如何搜集內(nèi)存泄漏信息
android 使用 LeakCanary 分析內(nèi)存泄漏原理(二)