android內存分配及泄露

Java 內存分配策略java

Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。程序員

•靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,而且在程序整個運行期間都存在,生命週期和整個應用相同。shell

•棧區 :當方法被執行時,方法體內的局部變量都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。在棧區通常存儲一些基本的數據類型(int, short, long, byte, float, double, boolean, char),類方法,對象地址,常量等等。數據庫

•堆區 :又稱動態內存分配,一般就是指在程序運行時直接 new 出來的內存。這部份內存在不使用時將會由 Java 垃圾回收器來負責回收,缺點是要在運行時分配內存,存取速度慢。         一般存儲一些經過new來建立的對象,可是該對象的地址通常保存在棧中.編程

查看內存使用狀況網絡

可使用AS的monitor實時觀察app運行是內存耗費狀況。 使用adb shell meminfo $包名或 $進程號 來顯示該應用的內存耗費狀況app

GC的原理: 異步

Java的內存管理就是對象的分配和釋放問題。在 Java 中,程序員須要經過關鍵字 new 爲每一個對象申請內存空間 (基本類型除外),全部的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由 GC 決定和執行的。在 Java 中,內存的分配是由程序完成的,而內存的釋放是由 GC 完成的,這種收支兩條線的方法確實簡化了程序員的工做。但同時,它也加劇了JVM的工做。這也是 Java 程序運行速度較慢的緣由之一。由於,GC 爲了可以正確釋放對象,GC 必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC 都須要進行監控。 監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象再也不被引用。 爲了更好理解 GC 的工做原理,咱們能夠將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。另外,每一個線程對象能夠做爲一個圖的起始頂點,例如大多程序從 main 進程開始執行,那麼該圖就是以 main 進程頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個(這些)對象再也不被引用,能夠被 GC 回收。socket

什麼是Java中的內存泄露:async

在上面描述了GC的原理,只要從根節點出發便利有向圖,只要不可達的節點都是GC對象。然而在現實的操做中,有些對象經過根節點可達,可是這些對象是無用的 ,即程序再也不會使用該對象。若是出現上述兩種狀況就能夠斷定內存泄露了。

檢測內存泄露的工具:

leakcanary 一個開源的工具,在檢測對象被destory後一一檢查其內部的對象是否還有未釋放的引用,若是有則提示泄露

常見的內存泄露:

一、靜態集合類引發內存泄漏: 像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,他們所引用的全部的對象Object也不能被釋放,由於他們也將一直被Vector等引用着。

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

 

二、當集合裏面的對象屬性被修改後,再調用remove()方法時不起做用。

public static void main(String[] args)
{
    Set set = new HashSet();
    Person p1 = new Person("唐僧","pwd1",25);
    Person p2 = new Person("孫悟空","pwd2",26);
    Person p3 = new Person("豬八戒","pwd3",27);
    set.add(p1);
    set.add(p2);
    set.add(p3);
    System.out.println("總共有:"+set.size()+" 個元素!"); 
    p3.setAge(2); 
    set.remove(p3); 
    set.add(p3); 
    System.out.println("總共有:"+set.size()+" 個元素!"); 
   for (Person person : set)
   {
        System.out.println(person);
   }
}

 

三、監聽器 在java 編程中,咱們都須要和監聽器打交道,一般一個應用當中會用到不少監聽器,咱們會調用一個控件的諸如addXXXListener()等方法來增長監聽器,但每每在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增長了內存泄漏的機會。

四、各類鏈接 好比數據庫鏈接(dataSourse.getConnection()),網絡鏈接(socket)和io鏈接,除非其顯式的調用了其close()方法將其鏈接關閉,不然是不會自動被GC 回收的。對於Resultset 和Statement 對象能夠不進行顯式回收,但Connection 必定要顯式回收,由於Connection 在任什麼時候候都沒法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會當即爲NULL。可是若是使用鏈接池,狀況就不同了,除了要顯式地關閉鏈接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另一個也會關閉),不然就會形成大量的Statement 對象沒法釋放,從而引發內存泄漏。這種狀況下通常都會在try裏面去的鏈接,在finally裏面釋放鏈接。

五、內部類和外部模塊的引用

private static Object inner;
   void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

 

六、單例模式 不正確使用單例模式是引發內存泄漏的一個常見問題,單例對象在初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部的引用,那麼這個對象將不能被JVM正常回收,致使內存泄漏


七、一些靜態的的變量持有局部對象
在程序中因爲靜態變量是和應用的生存週期保持一致的,可是在類中經過new一些局部對象賦值給這些靜態變量時,噹噹前類銷燬時這些局部對象也同樣會
跟着銷燬,可是因爲靜態變量持有這些對象的引用,跟應用的生存週期保持一致,因此這種狀況下會致使內存泄漏。
常見的一些賦值給靜態變量的對象:
經過findViewById獲取的view賦值給static view;
new一個對象賦值給static 變量
static activity 的賦值

 8.還有一些異步操做致使的泄露

在Android中有不少種異步操做,handler操做;new一個線程Runnable,asyncTask等等,經過這些方法開啓一個線程工做,若是有時候線程耗時很長而當前activity已經被destory了,因此這些線程還一直存在可是已是沒什麼必要了。

如何避免內存泄露:

一、對於一些靜態的變量,能夠經過弱引用賦予其對象,這樣在進行GC的時候,弱引用的對象同樣可以被回收。

 private static WeakReference<MainActivity> activityReference;
 void setStaticActivity() {
     activityReference = new WeakReference<MainActivity>(this);
}

這種狀況一樣適用於其餘相似的static變量。

二、對於一些匿名類致使的泄露可使用靜態內部類來代替,這樣因爲該類是靜態的跟應用擁有相同的生命週期

private static class NimbleTask extends AsyncTask<Void, Void, Void> {
    @Override protected Void doInBackground(Void... params) {
        while(true);
    }
}

void startAsyncTask() {
    new NimbleTask().execute();
}

對於開啓的線程也能夠若是在activity被destory的時候,不須要進行執行能夠在周期函數destory中將該thread進行中斷。

private Thread thread;

@Override
public void onDestroy() {
    super.onDestroy();
    if (thread != null) {
        thread.interrupt();
    }
}

void spawnThread() {
    thread = new Thread() {
        @Override public void run() {
            while (!isInterrupted()) {
            }
        }
    }
    thread.start();
}

三、對於一些監聽的操做,須要在destory中所有進行釋放

 

參考文章:

http://www.jianshu.com/p/c5ac51d804fa

https://juejin.im/entry/5747d70fc26a38006c5744d5

相關文章
相關標籤/搜索