天天看點

Android 性能篇 -- 帶你領略Android記憶體洩漏的前世今生基礎了解常見記憶體洩漏拓展 -- 相關知識點總結參考

這是一篇轉載文章,提供一個記憶體檢測工具 Leakcanary Square的一款Android/Java記憶體洩漏檢測工具

基礎了解

什麼是記憶體洩漏?

記憶體洩漏是當程式不再使用到的記憶體時,釋放記憶體失敗而産生了無用的記憶體消耗。記憶體洩漏并不是指實體上的記憶體消失,這裡的記憶體洩漏是指由程式配置設定的記憶體但是由于程式邏輯錯誤而導緻程式失去了對該記憶體的控制,使得記憶體浪費。

Java 記憶體配置設定政策

Java 程式運作時的記憶體配置設定政策有三種,分别是 靜态配置設定 、 棧式配置設定 和 堆式配置設定 ,對應的三種存儲政策使用的記憶體空間主要分别是 靜态存儲區(也稱方法區) 、 棧區 和 堆區 。

靜态存儲區(方法區):主要存放 靜态資料 、 全局 static 資料 和 常量。這塊記憶體在程式編譯時就已經配置設定好,并且在程式整個運作期間都存在。

棧區:當方法被執行時,方法體内的 局部變量 (其中包括基礎資料類型、對象的引用)都在棧上建立,并在方法執行結束時這些局部變量所持有的記憶體将會自動被釋放。因為棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有限。

堆區: 又稱動态記憶體配置設定,通常就是指在程式運作時直接 new 出來的記憶體,也就是 對象的執行個體。這部分記憶體在不使用時将會由 Java 垃圾回收器(GC)來負責回收。

棧與堆的差別

在方法體内定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧記憶體中配置設定的。當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量配置設定記憶體空間,當超過該變量的作用域後,該變量也就無效了,配置設定給它的記憶體空間也将被釋放掉,該記憶體空間可以被重新使用。

堆記憶體用來存放所有由 new 建立的對象(包括該對象其中的所有成員變量)和數組。在堆中配置設定的記憶體,将由 Java 垃圾回收器來自動管理。在堆中産生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆記憶體中的首位址,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來通路堆中的對象或者數組。

舉例說明:

public class Sample {
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();     // Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中,但 mSample2 指向的對象是存在于堆上
    }
}

Sample mSample3 = new Sample();             // mSample3 指向的對象實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1,而它自己存在于棧中。
           

Java是如何管理記憶體

Java的記憶體管理就是對象的配置設定和釋放問題。在 Java 中,程式員需要通過關鍵字 new 為每個對象申請記憶體空間 (基本類型除外),所有的對象都在堆 (Heap)中配置設定空間。另外,對象的釋放是由 GC 決定和執行的。在 Java 中,記憶體的配置設定是由程式完成的,而記憶體的釋放是由 GC 完成的,這種收支兩條線的方法确實簡化了程式員的工作。但同時,它也加重了JVM的工作。這也是 Java 程式運作速度較慢的原因之一。因為,GC 為了能夠正确釋放對象,GC 必須監控每一個對象的運作狀态,包括對象的申請、引用、被引用、指派等,GC 都需要進行監控。

監視對象狀态是為了更加準确地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。

Java中的記憶體洩漏

在Java中,記憶體洩漏就是存在一些被配置設定的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程式以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的記憶體洩漏,這些對象不會被GC所回收,然而它卻占用記憶體。

在C++中,記憶體洩漏的範圍更大一些。有些對象被配置設定了記憶體空間,然後卻不可達,由于C++中沒有GC,這些記憶體将永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,是以程式員不需要考慮這部分的記憶體洩露。

通過分析,我們得知,對于C++,程式員需要自己管理邊和頂點,而對于Java程式員隻需要管理邊就可以了(不需要管理頂點的釋放)。通過這種方式,Java提高了程式設計的效率。

是以,通過以上分析,我們知道在Java中也有記憶體洩漏,但範圍比C++要小一些。因為Java從語言上保證,任何對象都是可達的,所有的不可達對象都由GC管理。

對于程式員來說,GC基本是透明的,不可見的。雖然,我們隻有幾個函數可以通路GC,例如運作GC的函數System.gc(),但是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器一定會執行。因為,不同的JVM實作者可能使用不同的算法管理GC。通常,GC的線程的優先級别較低。JVM調用GC的政策也有很多種,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。除非在一些特定的場合,GC的執行影響應用程式的性能,例如對于基于Web的實時系統,如網絡遊戲等,使用者不希望GC突然中斷應用程式執行而進行垃圾回收,那麼我們需要調整GC的參數,讓GC能夠通過平緩的方式釋放記憶體,例如将垃圾回收分解為一系列的小步驟執行,Sun提供的HotSpot JVM就支援這一特性。

以下給出一個 Java 記憶體洩漏的典型例子:

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;   
}
           

在這個例子中,我們循環申請Object對象,并将所申請的對象放入一個 Vector 中,如果我們僅僅釋放引用本身,那麼 Vector 仍然引用該對象,是以這個對象對 GC 來說是不可回收的。是以,如果對象加入到Vector 後,還必須從 Vector 中删除,最簡單的方法就是将 Vector 對象設定為 null。

常見記憶體洩漏

永遠的單例

單例的使用在我們的程式中随處可見,因為使用它可以完美的解決我們在程式中重複建立對象的問題,不過可别小瞧它。由于 單例的靜态特性使得其生命周期跟應用的生命周期一樣長 ,是以一旦使用有誤,小心無限制的持有Activity的引用而導緻記憶體洩漏。

我們看個例子:

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;
    }
}
           

這是一個普通的單例模式,當建立這個單例的時候,由于需要傳入一個Context,是以這個Context的生命周期的長短至關重要! (實際常見)

1、如果此時傳入的是 Application 的 Context ,因為 Application 的生命周期就是整個應用的生命周期,是以這将沒有任何問題。

2、如果此時傳入的是 Activity 的 Context ,當這個 Context 所對應的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應用程式的生命周期,是以目前 Activity 退出時它的記憶體并不會被回收,這就造成洩漏了。

正确的方式(寫法一):

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;
    }
}
           

正确的方式(寫法二):

// 在你的 Application 中添加一個靜态方法,getContext() 傳回 Application 的 context

...

context = getApplicationContext();

...
   /**
     * 擷取全局的context
     * @return 傳回全局context對象
     */
    public static Context getContext(){
        return context;
    }

public class AppManager {

    private static AppManager instance;
    private Context context;
    
    private AppManager() {
        this.context = MyApplication.getContext(); // 使用Application 的context
    }
    
    public static AppManager getInstance() {
        if (instance == null) {
            instance = new AppManager();
        }
        return instance;
    }
}
           

靜态Activity

我們看一段代碼:

public class MainActivity extends AppCompatActivity {
    private static MainActivity activity;         // 這邊設定了靜态Activity,發生了記憶體洩漏
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticActivity();
                nextActivity();
            }
        });
    }
    void setStaticActivity() {
        activity = this;
    }

    void nextActivity(){
        startActivity(new Intent(this,RegisterActivity.class));
        SystemClock.sleep(1000);
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
           

在上面代碼中,我們聲明了一個靜态的 Activity 變量并且在 TextView 的 OnClick 事件裡引用了目前正在運作的 Activity 執行個體,是以如果在 activity 的生命周期結束之前沒有清除這個引用,則會引起記憶體洩漏。因為聲明的 activity 是靜态的,會常駐記憶體,如果該對象不清除,則垃圾回收器無法回收變量。

我們可以這樣解決:

protected void onDestroy() {
        super.onDestroy();
        activity = null;       // 在onDestory方法中将靜态變量activity置空,這樣垃圾回收器就可以将靜态變量回收
    }
           

靜态View

其實和靜态Activity頗為相似,我們看下代碼:

...
    private static View view;               // 定義靜态View
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticView();
                nextActivity();
            }
        });
    }
    void setStaticView() {
        view = findViewById(R.id.sv_view);
    }
    ...
           

View一旦被加載到界面中将會持有一個Context對象的引用,在這個例子中,這個context對象是我們的Activity,聲明一個靜态變量引用這個View,也就引用了activity,是以當activity生命周期結束了,靜态View沒有清除掉,還持有activity的引用,是以記憶體洩漏了。

protected void onDestroy() {
    super.onDestroy();
    view = null;         // 在onDestroy方法裡将靜态變量置空
} 
           

匿名類/AsyncTask

我們看下面的例子:

public class MainActivity extends AppCompatActivity {
    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        View aicButton = findViewById(R.id.at_button);
        aicButton.setOnClickListener(new View.OnClickListener() {
            @Override 
            public void onClick(View v) {
                startAsyncTask();
            }
        });
    }
}
           

上面代碼在activity中建立了一個匿名類 AsyncTask,匿名類和非靜态内部類相同,會持有外部類對象,這裡也就是activity,是以如果你在 Activity 裡聲明且執行個體化一個匿名的AsyncTask對象,則可能會發生記憶體洩漏,如果這個線程在Activity銷毀後還一直在背景執行,那這個線程會繼續持有這個Activity的引用進而不會被GC回收,直到線程執行完成。

我們可以這樣解決:

自定義靜态 AsyncTask 類,并且讓 AsyncTask 的周期和 Activity 周期保持一緻,也就是在 Activity 生命周期結束時要将 AsyncTask cancel 掉。

非靜态内部類

有的時候我們可能會在啟動頻繁的Activity中,為了避免重複建立相同的資料資源,可能會出現這種寫法:

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 {
        //...
    }
}
           

上面這段代碼在Activity内部建立了一個非靜态内部類的單例(mManager),每次啟動Activity時都會使用該單例的資料,這樣雖然避免了資源的重複建立,不過這種寫法卻會造成記憶體洩漏。

因為非靜态内部類預設會持有外部類的引用,而該非靜态内部類又建立了一個靜态的執行個體,該執行個體的生命周期和應用的一樣長,這就導緻了該靜态執行個體一直會持有該Activity的引用,導緻Activity的記憶體資源不能正常回收。

正确的做法為:

将該内部類設為靜态内部類或将該内部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。當然,Application 的 context 不是萬能的,是以也不能随便亂用,對于有些地方則必須使用 Activity 的 Context。

Handler

Handler 的使用造成的記憶體洩漏問題應該說是 最為常見 了,很多時候我們為了避免 ANR 而不在主線程進行耗時操作,在處理網絡任務或者封裝一些請求回調等api都借助Handler來處理,但 Handler 不是萬能的,對于 Handler 的使用代碼編寫不規範即有可能造成記憶體洩漏。另外,我們知道 Handler、Message 和 MessageQueue 都是互相關聯在一起的,萬一 Handler 發送的 Message 尚未被處理,則該 Message 及發送它的 Handler 對象将被線程 MessageQueue 一直持有。

由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一緻的。是以這種實作方式一般很難保證跟 View 或者 Activity 的生命周期保持一緻,故很容易導緻無法正确釋放。

public class SampleActivity extends Activity {

    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mLeakyHandler.postDelayed(new Runnable() {
            @Override
            public void run() { /* ... */ }
        }, 1000 * 60 * 10);

        // Go back to the previous Activity.
        finish();
    }
}
           

在該 SampleActivity 中聲明了一個延遲 10分鐘 執行的消息 Message,mLeakyHandler 将其 push 進了消息隊列 MessageQueue 裡。當該 Activity 被 finish() 掉時,延遲執行任務的 Message 還會繼續存在于主線程中,它持有該 Activity 的 Handler 引用,是以此時 finish() 掉的 Activity 就不會被回收了進而造成記憶體洩漏(因 Handler 為非靜态内部類,它會持有外部類的引用,在這裡就是指 SampleActivity)。

在 Activity 中避免使用非靜态内部類,比如上面我們将 Handler 聲明為靜态的,則其存活期跟 Activity 的生命周期就無關了。同時通過弱引用的方式引入 Activity,避免直接将 Activity 作為 context 傳進去,見下面代碼:

public class SampleActivity extends Activity {

    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();
            if (activity != null) {                              // 每次使用前注意判空
                // ...
            }
        }
    }

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

        // Go back to the previous Activity.
        finish();
    }
}
           

從上面的代碼中我們可以看出如何避免Handler記憶體洩漏,推薦使用 "靜态内部類 + WeakReference" 這種方式,每次使用前注意判空。

Java對引用的分類有Strong reference、SoftReference、WeakReference、PhatomReference四種。

級别 回收機制 用途 生存時間
從來不會 對象的一般狀态 JVM停止運作時終止
在記憶體不足時 聯合ReferenceQueue構造有效期短/占記憶體打/生命周期長的對象的二級高速緩沖器(記憶體不足時才情況) 記憶體不足時終止
在垃圾回收時 聯合ReferenceQueue構造有效期短/占記憶體打/生命周期長的對象的一級高速緩沖器(系統發生gc時清空) gc運作後終止
聯合ReferenceQueue來跟蹤對象被垃圾回收期回收的活動

在Android應用的開發中,為了防止記憶體溢出,在處理一些占用記憶體大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術。

軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛拟機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象清單,進而為緩沖器清除已失效的軟/弱引用。

Thread

看個範例:

public class SampleActivity extends Activity {
    void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View tButton = findViewById(R.id.t_button);
        tButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                spawnThread();
            }
        });
    }
}
           

其實這邊發生的記憶體洩漏原因跟AsyncTask是一樣的。

我們自定義Thread并聲明成static這樣可以嗎?其實這樣的做法并不推薦,因為Thread位于GC根部,DVM會和所有的活動線程保持hard references關系,是以運作中的Thread絕不會被GC無端回收了,是以正确的解決辦法是在自定義靜态内部類的基礎上給線程加上取消機制,是以我們可以在Activity的onDestroy方法中将thread關閉掉。

Timer Tasks

public class SampleActivity extends Activity {
    void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        },1000);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View ttButton = findViewById(R.id.tt_button);
        ttButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            scheduleTimer();
            }
        });
    }
}
           

這裡記憶體洩漏在于Timer和TimerTask沒有進行Cancel,進而導緻Timer和TimerTask一直引用外部類Activity。

在适當的時機進行Cancel。

Sensor Manager

public class SampleActivity extends Activity {
    void registerListener() {
           SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
           Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
           sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View smButton = findViewById(R.id.sm_button);
        smButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                registerListener();
            }
        });
    }
}
           

通過Context調用getSystemService擷取系統服務,這些服務運作在他們自己的程序執行一系列背景工作或者提供和硬體互動的接口,如果Context對象需要在一個Service内部事件發生時随時收到通知,則需要把自己作為一個監聽器注冊進去,這樣服務就會持有一個Activity,如果開發者忘記了在Activity被銷毀前登出這個監聽器,這樣就導緻記憶體洩漏。

在onDestroy方法裡登出監聽器。

盡量避免使用 static 成員變量

如果成員變量被聲明為 static,那我們都知道其生命周期将與整個app程序生命周期一樣。

這會導緻一系列問題,如果你的app程序設計上是長駐記憶體的,那即使app切到背景,這部分記憶體也不會被釋放。按照現在手機app記憶體管理機制,占記憶體較大的背景程序将優先回收,如果此app做過程序互保保活,那會造成app在背景頻繁重新開機。當手機安裝了你參與開發的app以後一夜時間手機被消耗空了電量、流量,你的app不得不被使用者解除安裝或者靜默。

這裡修複的方法是:

不要在類初始時初始化靜态成員。可以考慮lazy初始化(使用時初始化)。架構設計上要思考是否真的有必要這樣做,盡量避免。如果架構需要這麼設計,那麼此對象的生命周期你有責任管理起來。

避免 override finalize()

1、finalize 方法被執行的時間不确定,不能依賴與它來釋放緊缺的資源。時間不确定的原因是:

虛拟機調用GC的時間不确定

Finalize daemon線程被排程到的時間不确定

2、finalize 方法隻會被執行一次,即使對象被複活,如果已經執行過了 finalize 方法,再次被 GC 時也不會再執行了,原因是:

含有 finalize 方法的 object 是在 new 的時候由虛拟機生成了一個 finalize reference 在來引用到該Object的,而在 finalize 方法執行的時候,該 object 所對應的 finalize Reference 會被釋放掉,即使在這個時候把該 object 複活(即用強引用引用住該 object ),再第二次被 GC 的時候由于沒有了 finalize reference 與之對應,是以 finalize 方法不會再執行。

3、含有Finalize方法的object需要至少經過兩輪GC才有可能被釋放。

集合對象及時清除

我們通常會把一些對象的引用加入到集合容器(比如ArrayList)中,當我們不再需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

是以在退出程式之前,将集合裡面的東西clear,然後置為null,再退出程式,如下:

private List<String> nameList;
private List<Fragment> list;

@Override
public void onDestroy() {
    super.onDestroy();
    if (nameList != null){
        nameList.clear();
        nameList = null;
    }
    if (list != null){
        list.clear();
        list = null;
    }
}
           

webView

當我們不再需要使用webView的時候,應該調用它的destory()方法來銷毀它,并釋放其占用的記憶體,否則其占用的記憶體長期也不能回收,進而造成記憶體洩漏。

為webView開啟另外一個程序,通過AIDL與主線程進行通信,webView所在的程序可以根據業務的需要選擇合适的時機進行銷毀,進而達到記憶體的完整釋放。

資源未關閉

對于使用了BraodcastReceiver,ContentObserver,File,遊标 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出,否則這些資源将不會被回收,造成記憶體洩漏。

拓展 -- 相關知識點

static 關鍵字

使用static聲明屬性

如果在程式中使用static聲明屬性,則此屬性稱為全局屬性(也稱靜态屬性),那麼聲明成全局屬性有什麼用?我們看下代碼:

class Person {
    String name;
    int age;
    static String country = "A城";
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void info() {
        System.out.println("姓名:" + this.name + ",年齡:" + this.age + ",城市:" + country);
    }
};

public class Demo {
    public static void main(String agrs[]) {
        Person p1 = new Person("張三", 30);
        Person p1 = new Person("李四", 31);
        Person p1 = new Person("王五", 32);
        Person.country = "B城";
        p1.info();
        p2.info();
        p3.info();
    }
}
           

以上程式很清晰的說明了static聲明屬性的好處,需要注意一點的是,類的公共屬性應該由類進行修改是最合适的(當然也可以p1.country = ...),有時也就把使用static聲明的屬性稱為類屬性。

使用static聲明方法

直接看下代碼就清楚了:

class Person {
    private String name;
    private int age;
    private static String country = "A城";
    public static void setCountry(String C) {
        country = c;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void info() {
        System.out.println("姓名:" + this.name + ",年齡:" + this.age + ",城市:" + country);
    }
    public static String getCountry() {
        return country;
    }
};

public class Demo {
    public static void main(String agrs[]) {
        Person p1 = new Person("張三", 30);
        Person p1 = new Person("李四", 31);
        Person p1 = new Person("王五", 32);
        Person.setCountry("B城");
        p1.info();
        p2.info();
        p3.info();
    }
}
           

【特殊說明】

非static聲明的方法可以調用static聲明的屬性或方法

static聲明的方法不能調用非static聲明的屬性或方法

比如以下代碼就會出錯:

class Person {
    private static String country = "A城";
    private String name = "Hello";
    public static void sFun(String C) {
        System.out.println("name = " + name);       // 錯誤,不能調用非static屬性
        fun();                                      // 錯誤,不能調用非static方法
    }
    public void fun() {
        System.out.println("World!!!");
    }
};
           

内部類

基本定義

我們都知道,在類内部可以定義成員變量與方法,同樣,在類内部也可以定義另一個類。如果在類Outer的内部定義一個類Inner,此時類Inner就稱為内部類,而類Outer則稱為外部類。

内部類可聲明成 public 或 private。當内部類聲明成 public 或 private時,對其通路的限制與成員變量和成員方法完全相同。

内部類的定義格式

辨別符 class 外部類的名稱 {
    // 外部類的成員
    辨別符 class 内部類的名稱 {
        // 内部類的成員
    }
}
           

内部類的好處

可以友善地通路外部類中的私有屬性!

靜态内部類

使用static可以聲明屬性或方法,而使用static也可以聲明内部類,用static聲明的内部類就變成了外部類,但是用static聲明的内部類不能通路非static的外部類屬性。

比如如下例子:

class Outer {
    private static String info = "Hello World!!!";    // 如果此時info不是static屬性,則程式運作報錯
    static class Inner {
        public void print() {
            System.out.println(info);
        }
    };
};

public class InnerClassDemo {
    public static void main(String args[]) {
        new Outer.Inner().print();
    }
}
           

執行結果:

Hello World!!!
```
      

在外部通路内部類

一個内部類除了可以通過外部類通路,也可以直接在其他類中進行調用。

【在外部通路内部類的格式】

外部類.内部類 内部類對象 = 外部類執行個體.new 内部類();
           
class Outer {
    private String info = "Hello World!!!";  
    class Inner {
        public void print() {
            System.out.println(info);
        }
    };
};

public class InnerClassDemo {
    public static void main(String args[]) {
        Outer out = new Out();              // 執行個體化外部類對象
        Outer.Inner in = out.new Inner();   // 執行個體化内部類對象
        in.print();                         // 調用内部類方法
    }
}
           

在方法中定義内部類

除了在外部類中定義内部類,我們也可以在方法中定義内部類。但是需要注意的是,在方法中定義的内部類不能直接通路方法中的參數,如果方法中的參數想要被内部類通路,則參數前必須加上final關鍵字。

class Outer {
    private String info = "Hello World!!!";
    public void fun(final int temp) {      // 參數要被通路必須用final聲明
        class Inner {
            public void print() {
                System.out.println("類中的屬性:" + info);
                System.out.println("方法中的參數:" + temp);
            }
        };
        new Inner().print();
    }
};

public class InnerClassDemo {
    public static void main(String args[]) {
        new Outer().fun(30);               // 調用外部類方法              
    }
}
           

總結

在開發中,記憶體洩漏最壞的情況是app耗盡記憶體導緻崩潰,但是往往真實情況不是這樣的,相反它隻會耗盡大量記憶體但不至于閃退,可配置設定的記憶體少了,GC便會更多的工作釋放記憶體,GC是非常耗時的操作,是以會使得頁面卡頓。我們在開發中一定要注意當在Activity裡執行個體化一個對象時看看是否有潛在的記憶體洩漏,一定要經常對記憶體洩漏進行檢測。

參考

01.

https://developer.android.goo...

02.

03.

https://www.jianshu.com/p/f77...

原文位址:

https://segmentfault.com/a/1190000016333862