1. 程式人生 > >android垃圾回收機制

android垃圾回收機制

目錄

1."垃圾的判定"

1)JDK1.1 前的判定規則-引用計數演算法

2)目前在用的可達性分析演算法

2.回收演算法

1)標記清除演算法

2)複製演算法

3)標記整理演算法

4)分代回收演算法

3.記憶體優化


垃圾記憶體不及時回收,則執行時的可用記憶體會越來越少,最終導致OOM(記憶體溢位)。而垃圾回收(GC),主要可從兩個方面探討:

1.怎麼判定是"垃圾"?

2.怎麼回收的?

1."垃圾的判定"

垃圾的判定主要是針對堆記憶體中的物件、陣列等,當物件超出作用域,不被引用時,就可以被認定為是垃圾。

1)JDK1.1 前的判定規則-引用計數演算法

當物件建立時候,都被繫結一個計數器,當物件被引用則計數+1,引用失效或超出作用域則計數-1,計數為0時即被Jvm判定為"垃圾"。效率雖高,但是A和B物件如果同時互相引用,計數都為1,即使A、B不再被使用,Jvm也不會檢測到。而且,每次物件被引用時等操作還要觸發計數,因此,額外開銷在所難免,也就被棄用了。

2)目前在用的可達性分析演算法

從"GC Root“物件開始,向下搜尋,遇到可達的物件,就會形成一條鏈,將可達物件與"GC Root“關聯。最終不與任何"GC Root“鏈相關的物件,都會被判定為“垃圾”。如下物件可認定為"GC Root"物件:

1.虛擬機器棧(棧幀中的本地變量表)中引用的物件

2.方法區中靜態屬性引用的物件

3.方法區中常量引用的物件

4.本地方法棧中JNI引用的物件

5.活著的執行緒Thread

2.回收演算法

1)標記清除演算法

首先,會先標記所有需要回收的物件,接著將被標記的不可用的物件清除。優點是演算法簡單,只需要清理被標記的地址空間。缺點也比較明顯,因為標記位置零散,很碎片化,導致被清除完後,可用的地址空間也零零散散。如果連續的可用記憶體空間少,那麼遇到大的記憶體物件需分配記憶體時,就很難分配到適宜的連續記憶體空間。

2)複製演算法

將記憶體空間平分為A、B兩塊,將A中存活的物件移動到B記憶體塊中,再對A塊所有物件進行清除。但是這個演算法也有明顯的缺點,那就是不管A區域或B區域有多少個存活物件,都需要將整塊記憶體分成兩個區域,意味著能夠真正使用的記憶體變成了一半,造成記憶體的利用率不高。

3)標記整理演算法

類似標記清理演算法,將存活的記憶體物件標記,並平移到記憶體空間一側,然後清理記憶體空間另側資料。此演算法解決了標記清除演算法的導致的記憶體空間碎片化的問題。但是,缺點也有,就是多出了將存活記憶體平移的操作,會降低清除的效率。

4)分代回收演算法

將物件進行代的劃分,把不同生命週期的物件放在不同的代上使用不同的垃圾回收方式。主要代劃分如下:

年輕代(例如:方法的區域性變數等)

年老代(例如:快取物件、單例物件等)

持久代(例如:載入過的類資訊)

由上可知:堆大小=新生代+老年代

a、持久代主要存放的是類資訊,所以與java物件的回收關係不大,與回收相關的是年輕代和年老代。

b、年輕代又被分為3個部分:Enden區和兩個Survivor區(From和to:這兩個區是對稱的,沒先後關係)

記憶體大小比值為------>Enden區:From區:to區=8:1:1

新建立的物件會被放入Eden區,Eden區滿了會執行小GC,會把存活物件轉移(複製演算法)到From或者to區,假設為From區。接著,Eden區接著儲存新建立的物件,再次滿時,Eden區和From區會執行小GC,將存活物件轉移到to區。每次小GC後,存活物件都會在From或者to區,但是From或者to區也不會一直做苦力,多次轉移相同的存活物件。因此,From和to區會有一個閾值,如果轉移相同存活物件的次數超過閾值,這些物件就會被存放到老年代。

c、老年代默默存著年輕代轉移過來的資料,當空間被用完時,會觸發大GC(“標記-清除”或者“標記-整理”演算法)。由於回收時間比較長,因此需要避免頻繁觸發大GC。

3.記憶體優化

1)物件無需引用,則置空為null,方便檢測被垃圾記憶體

2)多用基本資料型別,少用它們的引用資料型別。例如Integer會比int所佔大

3)少用static修飾變數,被修飾的全域性性的變數是不會被GC回收的

4)字串拼接要用StringBuffer(用append拼接)替代String,例如String str =str1+str2+str3+str4+str5,每多一個“+”,就會多建立一個物件。

5)bitmap、遊標Cursor、IO或者檔案流等不用時候,記得回收。尤其是圖片的載入而佔用記憶體較大,可以做圖片質量或者物理大小的壓縮。

6)避免在頻繁繪製的ondraw方法中建立物件

7)Hashmap由於建立時候記憶體生成16位儲存Entry節點的陣列,一個Entry佔空間32B。也就是說即使裡面沒有任何元素,也要分別一塊記憶體空間給它。且儲存資料每次大於當前容量最大值,Hashmap都會以X2容量的方式去擴容。所以在Android中,HashMap是比較費記憶體的。可以嘗試android平臺自帶的SparseArray或者ArrayMap

詳解:https://blog.csdn.net/u010687392/article/details/47809295

8)防止單例類長久持有不用物件的引用,導致物件無法回收,特別是傳入上下文物件的單例類,可嘗試傳入ApplicationContext

9)非靜態內部類導致記憶體洩露,比如Activity中建立的Handler,可以嘗試弱引用去拿到外部的物件引用。

詳解:https://blog.csdn.net/qq_37321098/article/details/81535449

10)廣播的登出,頁面Activity退出時未執行完的Thread的登出或者Timer還在執行定時任務的登出等等。

11)Activity結束時候,需cancel掉屬性動畫。

12)webview退出的銷燬

    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();

記得以前,webview如果內部播放視訊,有背景聲音,即使做了銷燬,聲音也還在,後來是頁面銷燬時,先reload過載webview去關閉聲音,再銷燬webview。有好方法,可以告訴下哦,謝謝!