Java gc(垃圾回收機制)小結,以及Android優化建議

Wing_Li發表於2018-10-24

如果本文幫助到你,本人不勝榮幸,如果浪費了你的時間,本人深感抱歉。 希望用最簡單的大白話來幫助那些像我一樣的人。如果有什麼錯誤,請一定指出,以免誤導大家、也誤導我。 本文來自:www.jianshu.com/users/320f9… 感謝您的關注。

Java的記憶體是不用我們開發者自己來管理的,這個大家都知道,但是那它到底是怎麼運作的呢? 我們都知道GC,也就是垃圾回收機制,但到底什麼是GC。 我們一起來看看。

什麼是GC

垃圾回收是一種自動的儲存管理機制。當一些被佔用的記憶體不再需要時,就應該予以釋放,以讓出空間,這種儲存資源管理,稱為垃圾回收(garbage collection)。垃圾回收器可以讓程式設計師減輕許多負擔,也減少程式設計師犯錯的機會。 我們來主要看看Java的gc機制。

整個Java堆可以切割成為三個部分:

  1. Young(年輕代):
    1. Eden(伊利園):存放新生物件。
    2. Survivor(倖存者):存放經過垃圾回收沒有被清除的物件。
  2. Tenured(老年代):物件多次回收沒有被清除,則移到該區塊。
  3. Perm:存放載入的類別還有方法物件。

GC會造成什麼影響

在開始學習GC之前你應該知道一個詞:stop-the-world。不管選擇哪種GC演算法,stop-the-world都是不可避免的。 也就是說,當垃圾回收開始清理資源時,其餘的所有執行緒都會被停止。所以,我們要做的就是儘可能的讓它執行的時間變短。如果清理的時間過長,在我們的應用程式中就能感覺到明顯的卡頓。

什麼情況下GC會執行

因為它對系統影響很明顯,所以它到底在什麼時候執行呢?

總的來說,有兩個條件會觸發主GC:

  1. 當應用程式空閒時,即沒有應用執行緒在執行時,GC會被呼叫。因為GC在優先順序最低的執行緒中進行,所以當應用忙時,GC執行緒就不會被呼叫,但以下條件除外。
  2. Java堆記憶體不足時,GC會被呼叫。當應用執行緒在執行,並在執行過程中建立新物件,若這時記憶體空間不足,JVM就會強制地呼叫GC執行緒,以便回收記憶體用於新的分配。若GC一次之後仍不能滿足記憶體分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報“out of memory”的錯誤,Java應用將停止。

由於是否進行主GC由JVM根據系統環境決定,而系統環境在不斷的變化當中,所以主GC的執行具有不確定性,無法預計它何時必然出現,但可以確定的是對一個長期執行的應用來說,其主GC是反覆進行的。

垃圾回收的一般步驟

之前已經瞭解到Java堆被主要分成三個部分,而垃圾回收主要是在Young(年輕代)和Tenured(老年代)工作。 而 年輕代 又包括 Eden(伊利園)和兩個Survivor(倖存者)。 下面我們就來看看這些空間是如何進行互動的:

1、首先,所有新生成的物件都是放在年輕代的Eden分割槽的,初始狀態下兩個Survivor分割槽都是空的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件。

Java gc(垃圾回收機制)小結,以及Android優化建議

2、當Eden區滿的的時候,小垃圾收集就會被觸發。

Java gc(垃圾回收機制)小結,以及Android優化建議

3、當Eden分割槽進行清理的時候,會把引用物件移動到第一個Survivor分割槽,無引用的物件刪除。

Java gc(垃圾回收機制)小結,以及Android優化建議

4、在下一個小垃圾收集的時候,在Eden分割槽中會發生同樣的事情:無引用的物件被刪除,引用物件被移動到另外一個Survivor分割槽(S1)。此外,從上次小垃圾收集過程中第一個Survivor分割槽(S0)移動過來的物件年齡增加,然後被移動到S1。當所有的倖存物件移動到S1以後,S0和Eden區都會被清理。注意到,此時的Survivor分割槽儲存有不同年齡的物件。

Java gc(垃圾回收機制)小結,以及Android優化建議

5、在下一個小垃圾收集,同樣的過程反覆進行。然而,此時Survivor分割槽的角色發生了互換,引用物件被移動到S0,倖存物件年齡增大。Eden和S1被清理。

Java gc(垃圾回收機制)小結,以及Android優化建議

6、這幅圖展示了從年輕代到老年代的提升。當進行一個小垃圾收集之後,如果此時年老物件此時到達了某一個個年齡閾值(例子中使用的是8),JVM會把他們從年輕代提升到老年代。

Java gc(垃圾回收機制)小結,以及Android優化建議

7、隨著小垃圾收集的持續進行,物件將會被持續提升到老年代。

Java gc(垃圾回收機制)小結,以及Android優化建議

8、這樣幾乎涵蓋了年輕一代的整個過程。最終,在老年代將會進行大垃圾收集,這種收集方式會清理-壓縮老年代空間。

Java gc(垃圾回收機制)小結,以及Android優化建議


也就是說,剛開始會先在新生代內部反覆的清理,頑強不死的移到老生代清理,最後都清不出空間,就爆炸了。

與堆配置相關的引數

引數 描述
-Xms JVM啟動的時候設定初始堆的大小
-Xmx 設定最大堆的大小
-Xmn 設定年輕代的大小
-XX:PermSize 設定持久代的初始的大小
-XX:MaxPermSize 設定持久代的最大值

優化建議:

根據GC的工作原理,我們可以通過一些技巧和方式,讓GC執行更加有效率

  1. 最基本的建議就是儘早釋放無用物件的引用。大多數程式設計師在使用臨時變數的時候,都是讓引用變數在退出活動域(scope)後,自動設定為 null。好的做法是:如果程式允許,儘早將不用的引用物件賦為null,這樣可以加速GC的工作;
  2. 儘量少用finalize函式。finalize函式是Java提供給程式設計師一個釋放物件或資源的機會。但是,它會加大GC的工作量,因此儘量少採用finalize方式回收資源;
  3. 如果需要使用經常使用的圖片,可以使用SoftReference型別。它可以儘可能將圖片儲存在記憶體中,供程式呼叫,而不引起OutOfMemory;
  4. 注意集合資料型別,包括陣列,樹,圖,連結串列等資料結構,這些資料結構對GC來說,回收更為複雜,所以使用結束應立即置為null,不要等堆在一起。另外,注意一些全域性的變數,以及一些靜態變數。這些變數往往容易引起懸掛物件(dangling reference),造成記憶體浪費;
  5. 當程式有一定的等待時間(注意,是有一定等待時間時),程式設計師可以手動執行System.gc(),通知GC執行,但是Java語言規範並不保證GC一定會執行。使用增量式GC可以縮短Java程式的暫停時間。System.gc(); Runtime.getRuntime().gc() 這個方法對資源消耗較大盡量不要手動去呼叫這個方法,不然可能引起程式的明顯示卡頓
  6. 儘量使用StringBuffer,而不用String來累加字串
  7. 能用基本型別如int,long,就不用Integer,Long物件。基本型別變數佔用的記憶體資源比相應物件佔用的少得多;
  8. 儘量少用靜態物件變數,靜態變數屬於全域性變數,不會被GC回收,它們會一直佔用記憶體;
  9. 分散物件建立或刪除的時間,集中在短時間內大量建立新物件,特別是大物件,會導致突然需要大量記憶體,JVM在面臨這種情況時,只能進行主GC,以回收記憶體或整合記憶體碎片,從而增加主GC的頻率。

合理使用 軟引用 和 弱引用

清除 將引用物件的 referent 域設定為 null ,並將引用類在堆中引用的物件宣告為 可結束的。 StrongReference 強引用:正常的物件,一直不會清理,直到爆炸。 SoftReference 軟引用:記憶體不夠的時候,遇到了就清了。 WeakReference 弱引用:只要遇到了就清了,注意:可能清了好幾次,都沒遇到。 PhantomReference 虛引用:虛顧名思義就是沒有的意思,建立虛引用之後通過get方法返回結果始終為null。

PhantomReference 必須與 ReferenceQueue 類一起使用。需要 ReferenceQueue 是因為它能夠充當通知機制。當垃圾收集器確定了某個物件是虛可及物件時, PhantomReference 物件就被放在它的 ReferenceQueue 上。將 PhantomReference 物件放在 ReferenceQueue 上也就是一個通知,表明 PhantomReference 物件引用的物件已經結束,可供收集了。這使您能夠剛好在物件佔用的記憶體被回收之前採取行動。

給個軟引用的例子:

//首先定義一個HashMap,儲存軟引用物件。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
 
//再來定義一個方法,儲存Bitmap的軟引用到HashMap。
public void addBitmapToCache(String path) {
    // 強引用的Bitmap物件
    Bitmap bitmap = BitmapFactory.decodeFile(path);
    // 軟引用的Bitmap物件
    SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
    // 新增該物件到Map中使其快取
    imageCache.put(path, softBitmap);
}


//獲取的時候,可以通過SoftReference的get()方法得到Bitmap物件。
public Bitmap getBitmapByPath(String path) {
    // 從快取中取軟引用的Bitmap物件
    SoftReference<Bitmap> softBitmap = imageCache.get(path);
    // 判斷是否存在軟引用
    if (softBitmap == null) {
        return null;
    }
    // 取出Bitmap物件,如果由於記憶體不足Bitmap被回收,將取得空
    Bitmap bitmap = softBitmap.get();
    return bitmap;
}
複製程式碼

參考及學習連結:

www.cnblogs.com/shudonghe/p… Java 垃圾回收機制技術詳解 blog.csdn.net/lu100528736… Java 垃圾回收機制 (GC) 詳解、意義、建議 blog.csdn.net/ithomer/art… Java 記憶體模型及GC原理 www.codeceo.com/article/jav… Java GC專家系列1:理解Java垃圾回收 www.codeceo.com/article/jav… Java GC專家系列2:Java 垃圾回收的監控 www.codeceo.com/article/jav… Java GC 專家系列3:GC調優實踐

Android效能優化-記憶體洩漏的8個Case

blog.csdn.net/huoyin/arti… Java中三個引用類SoftReference 、 WeakReference 和 PhantomReference的區別

相關文章