深刻理解Java中的內存泄漏

理解Java中的內存泄漏,咱們首先要清楚Java中的內存區域分配問題和內存回收的問題本文將分爲三大部分介紹這些內容。java

Java中的內存分配

Java中的內存區域主要分爲線程共享的和線程私有的兩大區域: android

  • Java堆:在虛擬機啓動時建立,是全部線程共享的一塊內存區域。存放了全部的new出來的對象的實例和數組,對象的reference則在虛擬機棧上。
  • 方法區:與Java堆同樣,是各個線程共享的內存區域,用於存儲被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據
  • 虛擬機棧:每一個方法在執行過程當中都會建立一個棧幀,用來保存局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從開始執行到執行結束都對應着一個棧幀在虛擬機中從入棧到出棧的過程。咱們一般說的棧內存就是指的是虛擬機棧的本地變量表部分,主要存儲了基本數據類型和對象的引用。
  • 程序計數器:是線程執行字節碼文件位置的指示器,用於線程切換後,再次切換回來能準確執行上次執行到的字節碼文件的位置。
  • 本地方法棧:和虛擬機棧相似,只是記錄的是Native方法。

介紹了Java的內存分配問題,經過一段代碼來進行一下總結算法

public static void main(String[] args) {
         Animal animal = new Animal();
    }
publci class Animal{
    public static String address = "the earth"
    public Animal(){
        
    }
}
複製代碼

在main方法中,咱們new Animal(),首先,檢查內存中是否加載了Animal.class,若是沒有加載,則會先將Animal.class加載到方法區中,同時在方法區開闢一塊內存區域,用於存儲static 的"the earth"(這裏更能清晰的理解static這個關鍵字,就是static修飾的變量是屬於類的,而不是屬於類的某一個對象的),隨後在Java堆中開闢一塊內存區域,用於存儲new Animal()這個對象,在虛擬機棧中的animal會指向這個對象的地址。數組

Java中的內存回收機制

Java內存回收主要是指的是Java堆上的已經分配給對象的內存回收,判斷Java堆上的內存是否被回收,主要是經過可達性分析算法:從一系列能夠做爲 GC Roots 的對象開始向下搜索,搜索走過的路徑稱爲引用鏈,當GC回收時,一個對象沒有經過引用鏈與任何GC Roots對象鏈接,則這個對象就能夠被回收了。可做爲GC Roots對象的有如下幾種:bash

  • 虛擬機棧的本地變量表中引用的對象。
  • 方法區中靜態屬性和常量引用的對象。
  • 本地Native方法引用的對象。

須要注意的是GC Roots並非一直能夠做爲GC Roots的,eg:網絡

public void testGc(){
    Person person = new Person();
}
複製代碼

根據GC Roots的定義,new Person()這個對象被person所引用,person在虛擬機棧中,因此new Person()這個對象是做爲了GC Roots的,可是當這個testGc()方法執行完成,person釋放內存,new Person()這個對象就沒有其餘的引用,就再也不是GC Roots。ide

Java中的內存泄漏

內存泄漏須要和內存溢出區分開來,內存泄漏是指:部份內存咱們再也不須要了,可是虛擬機不能回收,泄漏過多就會形成內存溢出。就是部分對象咱們已經用不上了,可是這些對象和GC Roots存在必定程度上的引用關係,致使不能被垃圾回收機制回收。ui

幾種常見的內存泄漏

  • 非靜態內部類的靜態實例
public class InnerStactivity extends AppCompatActivity {

    private static Object ininerClass;
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_stactivity);

    }

    class InnerClass{

    }

    public void startInnerClass(){

        ininerClass = new InnerClass();

    }

    /**
     * 建立了InnerClass的靜態實例引用
     * @param view
     */
    public void createInner(View view) {

        startInnerClass();

    }
}
複製代碼

當static Object ininerClass指向了newInnerClass()這個對象時,這個對象就能夠做爲了GC Roots,同時非靜態的內部類會持有外部類的引用,InnerStactivity就會在GC Roots的引用鏈上,當咱們須要離開這個InnerStactivity,而且再也不須要這個InnerStactivity時,這個InnerStactivity並不能被回收。this

  • 匿名內部類的靜態實例spa

  • Handler內存泄漏

public class HandlerActivity extends AppCompatActivity {

    Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mHandler= new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                show();
            }
        };
    }

    public void sendMessage(View view) {
        mHandler.sendEmptyMessageDelayed(1,1000*30);
        finish();
    }

    public void show(){

    }

}
複製代碼

能夠看到咱們這麼寫Android studio已經提示了警告,提示咱們應該用static修飾handler對象,不然會形成內存的泄漏,這是不容易犯的錯誤。

Handler這麼寫之因此會出現內存泄漏是由於:Message會持有一個對Handler的引用,當這個Handler是非靜態內部類的時候,又會持有一個對外部類的引用(好比Activity)。若是發送一條延時的Message,因爲這個Message會長期存在於隊列中,就會致使Handler長期持有對Activity的引用,從而引發視圖和資源泄漏。當你發送一條延時的Mesaage,而且把這個Message保存在某些地方(例如靜態結構中)備用,內存泄漏會變得更加嚴重。

咱們如今都經過static修飾 Handler類,並經過弱引用來和當前界面的Activity交互,並在onDestory()中去除Handler的全部的消息來規避可能出現的內存泄漏。

public class HandlerImproveActivity extends AppCompatActivity {

    Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mHandler= new ImproveHandler(HandlerImproveActivity.this);
    }

    public void sendMessage(View view) {
        mHandler.sendEmptyMessageDelayed(1,1000*30);
        finish();
    }

    private static class ImproveHandler extends Handler{

        private WeakReference<HandlerImproveActivity> mActivity;

        public ImproveHandler(HandlerImproveActivity improveActivity) {
            this.mActivity = new WeakReference<HandlerImproveActivity>(improveActivity);
        }


        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mActivity != null && mActivity.get() != null) {
                mActivity.get().show();
            }
        }
    }

    public void show(){

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mHandler != null){
            mHandler.removeCallbacksAndMessages(null);
        }

    }
}
複製代碼
  • 未正確使用Context 在開發中會有大量的使用到Context的地方,若是不是必須使用到Activity的Context(例如Dialog就必須使用到activity的Context),則均可以使用getApplicationContext()來替代。
AlertDialog alertDialog= new AlertDialog.Builder(this).create();
 Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT).show();
複製代碼
  • 註冊未取消: 在使用觀察者模式的時候,在register後,完成時要即時unRegister監聽器。在某個Activity界面使用Rxjava進行網絡請求,在離開這個頁面的時候必定要取消註冊。
  • 資源對象未關閉: 資源對象好比Cursor、File等,不使用的時候應該關閉它們。把他們的引用置爲null,而不關閉它們,每每會形成內存泄漏。所以,在資源對象不使用時,必定要確保它已經關閉,一般在finally語句中關閉,防止出現異常時,資源未被釋放的問題。

Android Developer中關於如何管理內存的連接 developer.android.com/topic/perfo…

相關文章
相關標籤/搜索