1. 程式人生 > >記憶體洩漏—出現情況,非靜態內部類對外部類引用持有的洩漏復現

記憶體洩漏—出現情況,非靜態內部類對外部類引用持有的洩漏復現

前言

  • 本文為製造一個”非靜態內部類對外部類的引用持有”洩漏並對其結果進行觀察作為學習使用,手段是製造洩漏,目的是瞭解洩漏產生的原因並未解決提供一種思路。
  • 本文基於的思想是:2個Activity,其中一個Activity的內部類被外部引用掛住,導致該Acitvity無法正常回收。

Code

倆個Activity,一個SplashActivity,一個LeakActivity。操作路徑是從Splash跳到LeakActivity。往返5次之後,手動Gc。

SplashActivity


    package zj.com;

    import android.content.Intent;
    import
android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import zj.com.rxjava_operators.R; public class SplashActivity extends AppCompatActivity { private LeakActivity.TestResource testResource; private LeakActivity.TestResource testResource2; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); findViewById(R.id.leakgo).setOnClickListener(new View.OnClickListener() { @Override public
void onClick(View v) { startActivity(new Intent(SplashActivity.this, LeakActivity.class)); if (null == testResource) testResource = new LeakActivity().new TestResource(1, "張三"); if (null == testResource2) testResource2 = new LeakActivity().new TestResource(2, "李四"); } }); } }

LeakActivity


    package zj.com;

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;

    import zj.com.rxjava_operators.R;

    public class LeakActivity extends AppCompatActivity {

        private  TestResource mResource = null;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_leak);

             mResource = new TestResource();

            //...
        }

        class TestResource {

            public TestResource() {

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }


            //        //...
            int age;
            String name;

            public TestResource(int age, String name) {
                this.age = age;
                this.name = name;
            }

            public int getAge() {
                return age;
            }

            public void setAge(int age) {
                this.age = age;
            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

        }
    }

程式碼分析

  • 可以看到,整個記憶體洩漏是由於LeakActivity的”非靜態內部類”引起的。
  • 你也注意到了,整個內部類TestResource,看起來挺彆扭的,空參構造內還開個執行緒休息10秒,我為啥要這麼寫?
  • 我測試時,遇見倆種情況使這個記憶體洩漏無法復現,(1)當我空參構造休眠1秒時,(2)當我的TestResource內部類裡面啥都沒寫時。為什麼?具體太細我也說不上,這個可能要從GC的演算法判斷和回收的機制說起,這裡我先TODO,簡單分析下,我覺得應該是JVM一個內部的機制(據說是指令優化?),使得GC回收監測到上述倆種情況就會將這類物件去回收掉,從而無法引起記憶體洩漏,但是值得注意的一點是,當我在空參構造內睡1秒而非是10秒時,GC這時會去回收這個物件,而10秒時候則不會去回收,我覺得應該是GC監測到了該內部類物件還有未執行完畢的任務時,就不會去回收。
  • 再看SplashActivity,每次點選的時候,我都會建立倆個TestResource的物件,這倆個物件主要是讓SplashActivity這個類從外部的因素去引用TestResource這個引用,從而引起記憶體洩漏。

總結

總結下,如果我們想要製造有內部類引起的記憶體洩漏:

  • 那麼這個內部類一定不能為空類
  • 在沒有外部影響的情況下,比如我在Splash內對Leak的內部類物件進行引用時,我們這時候就要讓Leak.TestResource擁有足夠的處理時間。
  • 在有外部因素的影響下,這時候我們則可以不必考慮讓TestResouce去長時間處理一個任務也可以復現記憶體洩漏。