android 內(nèi)存泄漏原因分析(一)

簡(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)存泄漏原理(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容