1. 程式人生 > >Android之記憶體洩露、記憶體溢位、記憶體抖動分析

Android之記憶體洩露、記憶體溢位、記憶體抖動分析

記憶體

JAVA是在JVM所虛擬出的記憶體環境中執行的,記憶體分為三個區:堆、棧和方法區。
棧(stack):是簡單的資料結構,程式執行時系統自動分配,使用完畢後自動釋放。優點:速度快。
堆(heap):用於存放由new建立的物件和陣列。在堆中分配的記憶體,一方面由java虛擬機器自動垃圾回收器來管理,另一方面還需要程式設計師提供修養,防止記憶體洩露問題。
方法區(method):又叫靜態區,跟堆一樣,被所有的執行緒共享。方法區包含所有的class和static變數。

Java GC

GC可以自動清理堆中不在使用(不在有物件持有該物件的引用)的物件。

在JAVA中物件如果再沒有引用指向該物件,那麼該物件就無從處理或呼叫該物件,這樣的物件稱為不可到達(unreachable)。垃圾回收用於釋放不可到達的物件所佔據的記憶體。

對android來說,記憶體使用尤為吃緊,最開始的app程序最大分配才8M的記憶體,漸漸增加到16M、32M、64M,但是和服務端相比還是很渺小的。如果物件回收不及時,很容易出現OOM錯誤。

記憶體洩露

什麼是記憶體洩露?程式通過new分配記憶體,在使用完畢後沒有釋放,造成記憶體佔用。這塊記憶體不受GC控制,無法通過GC回收。 主要表現在:當一個物件已經不再使用,本該被回收的,但是另外一個正在使用的物件持有它的引用從而就導致物件不能被回收。這種物件存在堆記憶體中,就產生了記憶體洩漏。

危害?記憶體洩漏對於app沒有直接的危害,即使app有發生記憶體洩漏的情況,也不一定會引起app崩潰,但是會增加app記憶體的佔用。記憶體得不到釋放,慢慢的會造成app記憶體溢位。

解決記憶體洩漏目的就是防止app發生記憶體溢位

記憶體洩露主要表現的當Activity在finish的時候,由於物件持有對Activity的引用,造成Activity沒有被及時回收。總結了下大致有5種情況造成記憶體洩露,(1)static變數、匿名類的使用 (2)執行緒執行處理(3)各種監聽回撥處置(4)Bitmap等回收處置(5)集合類只有增操作卻沒有減操作。 常見情況 1)外部類持有Activity的靜態引用
public class MainActivity extends AppCompatActivity {
    static Activity activity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CommUtil commUtil = CommUtil.getInstance(this);
    }
public class CommUtils {
    private static CommUtils instance;
    private Context context;

    private CommUtils(Context context) {
        this.context = context;
    }

    public static CommUtils getInstance(Context context) {
        if (instance == null) {
            instance = new CommUtils(context);
        }
        return instance;
    }
}
2)非同步執行耗時任務期間時,Thread、AsyncTask、TimeTask持有的Activty進行finish時,Activity例項不會被回收。
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<String, Void, String>() {
            @Override
            protected String doInBackground(String... params) {
                for (int i = 0; i < 15; i++) {
                    try {
                        Log.e("MainActivity2", "dddd" + i + MainActivity2.this.getLocalClassName());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
            }
        }.execute();
    }
3)Handler內部類造成記憶體洩露。 Handler為非靜態內部類時會隱式持有當前activity引用。當Activity被 finish()時,若Handler有未處理完或延遲的訊息(主要是Handler牽扯到執行緒問題),會造成activity不能被回收。
 MyHandler myHandler = new MyHandler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 50 * 1000);
    }

    class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

解決辦法:在Activity生命週期結束前,確保Handler移除訊息(mMyHanlder.removeCallbacksAndMessages(null);)或者使用靜態Handler內部類。 如:使用了弱引用替代強引用.
static MyHandler myHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myHandler = new MyHandler(this);
    }
    static class MyHandler extends Handler {
        WeakReference<Activity> mActivityReference;

        MyHandler(Activity activity) {
            mActivityReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            final Activity activity = mActivityReference.get();
            if (activity != null) {
                //....
            }
        }
    }
建議熟悉下:強引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)、虛引用(PhantomReference) 4)匿名內部類的使用。
public class DemoActivity extends AppCompatActivity {
    
    Runnable runnable = new Runnable() {
        @Override
        public void run() {

        }
    };
runnable預設會持有DemoActivity的引用。若Activity被finish的時候,如執行緒在使用runnable,則會造成記憶體洩露。 5)構造Adapter時沒有使用快取的 convertView
public View getView(int position, View convertView, ViewGroup parent) {
        View view = null;
        if (convertView == null)
            convertView = View.inflate(this, R.layout.item_layout, false);
        view = convertView;
        return view;
    }
6) 當使用了BraodcastReceiver、Cursor、Bitmap等資源時,若沒有及時釋放,則會引起記憶體洩漏。 7)集合類的不當使用。

更多記憶體洩露可以通過檢測工具發現。檢測工具主要有MAT、Memory Monitor 、Allocation Tracker 、Heap Viewer、LeakCanary

記憶體溢位

什麼是記憶體溢位?out of memory。 程式向系統申請的記憶體空間超出了系統能給的。記憶體洩露很容易引起OOM。

記憶體抖動

記憶體抖動是指在短時間內有大量的物件被建立或者被回收的現象,主要是迴圈中大量建立、回收物件。這種情況應當儘量避免。