2013-07-15 59 views
3

我有一個應用程序,它將一些數據放入佔用JVM中幾乎所有JVM內存的長鏈表中。插入新元素時,最後一個元素將被刪除,以便列表的大小始終爲常量。當我將JVM內存大小設置爲6GB時,我開始定期GC暫停:大約每10秒發生3.4秒。GC暫停長FIFO隊列應用程序的調優

我在4核和16GB RAM的Linux上使用Hotspot Java 1.7.0,64位。以下JVM參數傳遞: -Xmx6g -XX:+ PrintGC -XX:+ UseConcMarkSweepGC -XX:+ UseParNewGC

能否請您指點一些更好的選擇,以儘量減少GC暫停時間到大約100毫秒左右?我試圖找到這樣的選擇muself,但沒有成功。

來源如下:

LinkedList<long[]> list = new LinkedList<long[]>(); 

    // initial fill in 
    for(int i = 0; i < 16L*1024*1024; i ++) { 
     list.add(new long[16]); 
    } 

    System.out.printf("total: %5.1f free: %5.1f\n",((float)Runtime.getRuntime().totalMemory())/(1024*1024*1024), ((float)Runtime.getRuntime().freeMemory())/(1024*1024*1024)); 

    // the main stuff 
    for(;;) { 
     list.removeFirst(); 
     list.add(new long[16]); 
    } 

更新: 在下面的討論中,我意識到,人試圖表明,在代碼中的一些變化。所以我需要更多地解釋一下這個問題的背景。源代碼示例是一個合成的虛幻代碼。只是它很好地說明了很多舊的對象的問題。在嘗試實施具有一些插入和驅逐策略的高負載緩存解決方案時遇到此問題。這往往導致舊生代垃圾的問題。我的目標是使用JVM選項找到最佳解決方案。這裏我不想考慮代碼改進。我想,如果有一種神奇的GC參數組合讓我的例子能夠在100ms以下暫停工作,它可能也會解決更一般的問題,或者至少爲類似情況提供一些提示。

+0

您可以用-XX發表您的GC日誌:+ PrintGCDetails? –

+0

6GiB情況下插入的頻率和隊列長度是多少? –

+1

您正在快速生成垃圾,不僅僅是列表元素,而是'long [16]'塊,它們可能比列表元素大。他們需要收集。你只是想更頻繁地做少量的GC嗎? –

回答

2

我想嘗試用ArrayDequeue替換怪異的鏈表,因爲隊列大小是恆定的。

一個非常長的鏈表很容易導致性能問題的垃圾收集器,實現標記遞歸。收藏家可能會對可以迭代標記的大型陣列感到高興。

UPDATE

有一個不起眼的GC調整參數,可以幫助:

如果你做這個參數更大,它可能足以從翻轉成非增量模式停止CMS收集器,如果標記是深深的遞歸(因爲它很可能成爲一個可怕的鏈接列表)

但是,這樣做會增加JVM的整體內存使用量。我的「信封背面」的思想是,你需要一個至少192M字節的標記棧來標記一個帶有16M元素的鏈表。這需要乘以標記的GC線程的數量。


我的問題的目的不是要改變Java代碼。試想一下你有一個正確的java程序,它不會導致OutOfMemoryError。您必須在不更改代碼的情況下找到正確的JVM參數。其實我對這種暫停的原因有了解,只是我不知道如何調整JVM以使暫停小於100ms。

恐怕在這種情況下,你的目標可能無法實現(不是上述情況)。如果應用程序對GC非常不友好,則GC性能會很差。

無論如何,你的大目標是(應該)通過任何必要的手段來解決性能問題。修復程序。在這種情況下,解決方案可能還有其他好處;例如減少內存使用量。

+0

我的問題的目標不是更改Java代碼。試想一下你有一個正確的java程序,它不會導致OutOfMemoryError。您必須在不更改代碼的情況下找到正確的JVM參數。其實我對這種暫停的原因有了解,只是我不知道如何調整JVM以使暫停小於100ms。 –

+1

對ArrayDequeue +1,這是一個好主意,即使不會自己解決暫停 –

+1

@Maxim:對不起,這不是優化的工作方式,特別是對於像GC行爲那樣敏感的內存佈局。調優GC不會彌補算法選擇上的不足。您無需更改代碼即可削弱您的優化工作量。 –

0

你會遇到很多鏈接列表的麻煩。

當GC開始掃描堆以查找活動對象時,它會從列表的第一個元素開始,然後通過每個元素遞歸地迭代。如果您的列表中包含很多元素,則意味着無論如何,GC暫停都會很長。

您應該將您的list聲明爲Collection<long[]>,然後嘗試不同的實現,例如基於ArrayList的手動製作圓形陣列的ArrayDequeue

如果我在我的機器上按原樣運行程序,我將得到Full GC,因爲它比CMS更快地填充堆。您無法真正開始基於此小片段的JVM調優練習。

希望有幫助!

2

我正在研究原始集合庫Banana,它支持原始linked lists。 您的使用案例幾乎是香蕉發光的理想用例,但它可以做更多的事情(包括可變長度塊,您未使用但可能在現實世界中使用)。

這是該基準測試我的電腦上的結果:

Banana : 1269 ms elapsed 
Banana : total: 2.5 GB, free: 0.5 GB, used = 2.1 GB, Banana reports that it's actually using 2.1 GB 
Java : 13543 ms elapsed 
Java : total: 6.2 GB, free: 2.0 GB, used = 4.2 GB 

你可以看到,香蕉更快,佔用更少的內存。 (Java的內存將是,如果你本身運行的更好,但不運行香蕉函數首先

Java : 14426 ms elapsed 
Java : total: 5.8 GB, free: 1.9 GB, used = 3.9 GB 

但仍然沒有香蕉附近的任何地方。

package net.yadan.banana.list; 

public class LinkedListBenchmark { 
    public static void main(String[] args) { 
    banana(); 
    java(); 
    } 

    public static void banana() { 
    long t = System.currentTimeMillis(); 

    // initial list size 16m records, block size 32 (storage is int[], so we 
    // need 32 ints to hold 16 longs) 
    net.yadan.banana.list.LinkedList list = new LinkedList(16 * 1024 * 1024, 16 * 2, 0); 

    // initial fill in 
    for (int i = 0; i < 16L * 1024 * 1024; i++) { 
     list.appendTail(32); // similar to java list.add() which appends to the 
          // end of the list 
    } 

    // the main stuff 
    for (int i = 0; i < 16L * 1024 * 1024; i++) { 
     list.removeHead(); // similar to java list removeFirst() 
     list.appendTail(32); // similar to java list.add() which appends to the 
          // end of the list 
    } 

    System.out.println("Banana : " + (System.currentTimeMillis() - t) + " ms elapsed"); 
    float GB = 1024 * 1024 * 1024; 
    long total = Runtime.getRuntime().totalMemory(); 
    long free = Runtime.getRuntime().freeMemory(); 
    System.out 
     .printf(
      "Banana : total: %5.1f GB, free: %5.1f GB, used = %5.1f GB, Banana reports that it's actually using %5.1f GB\n", 
      total/GB, free/GB, (total - free)/GB, list.computeMemoryUsage()/GB); 
    } 

    public static void java() { 

    long t = System.currentTimeMillis(); 

    java.util.LinkedList<long[]> list = new java.util.LinkedList<long[]>(); 

    // initial fill in 
    for (int i = 0; i < 16L * 1024 * 1024; i++) { 
     list.add(new long[16]); 
    } 

    // the main stuff 
    for (int i = 0; i < 16L * 1024 * 1024; i++) { 
     list.removeFirst(); 
     list.add(new long[16]); 
    } 

    System.out.println("Java : " + (System.currentTimeMillis() - t) + " ms elapsed"); 
    float GB = 1024 * 1024 * 1024; 
    long total = Runtime.getRuntime().totalMemory(); 
    long free = Runtime.getRuntime().freeMemory(); 
    System.out.printf("Java : total: %5.1f GB, free: %5.1f GB, used = %5.1f GB\n", total/GB, free/GB, 
     (total - free)/GB); 
    } 
} 
+0

請您詳細介紹一下香蕉內存管理的內部機制。 –

+0

請看維基,它不是很大,並會回答你的問題(也有漂亮的圖片:)。 https://github.com/omry/banana/wiki –

+0

如果您在閱讀相關wiki頁面後有任何疑問,請告訴我。 –

1

你舉的例子是非常多的最垃圾的病理情況這個問題更好的解決方案是使用Disruptor,但是我從您的意見中看到,您不想要其他設計建議。

如果您提供了GC日誌,可能會有一些CMS調整選項用於生成事件好一點,但是沒有日誌很難說。是由於FullGC造成的暫停,還是由於備註階段造成的暫停?如果使用FullGC,可能CMS的起步時間不夠快。

你正在處理的真正問題是什麼,因爲這個人爲的似乎有點瘋狂?

如果你想有這樣的設計模式,那麼最適合你的JVM是Azul Zing。

0

完整的GC持續時間與對象引用數量成比例。

你可能需要2件事情發生:

1)可能改變設計像ArrayDeque

  • 使用基於陣列的集合(數效應)

  • 連載的對象,因爲他們進入隊列。如果你想在外面發送它們,這是特別有趣的 。這將使Full GC基本上減少到零。結賬https://code.google.com/p/fast-serialization/ OffHeapQueue(未發佈)爲例。

2) 我對如何調整GC大型靜態數據做了一些分析與綜合應用。 http://java-is-the-new-c.blogspot.com/這可能幫助或保存一段時間

1

在類似的場景中,我們添加爲通過-Xmx選項的內存的兩倍所需金額 ,並加入 -XX:CMSInitiatingOccupancyFraction = 50 -XX:+ UseCMSInitiatingOccupancyOnly 選項。

在這種情況下,當舊的gen被50%滿(即我們強制更早的GC)並且它有足夠的可用內存來做快速碎片整理時,JVM執行GC。

此外,您可能會發現這些標記-XX:+ CMSConcurrentMTEnabled -XX:+ CMSScavengeBeforeRemark也有用。

0

正如其他評論者已經指出,你的示例打破了「大多數對象年輕化」的世代垃圾收集的基本先決條件。您將所有物體保持活性時間相當長,這會使世代GC失效。

如果你真的只需要調整JVM運行示例,因爲它是我建議你通過設置-XX:NewSize-XX:MaxNewSize既像切換到ParNew收集(使用-XX:+UseParNewGC),使新一代相當小200m300m

注意,像CMS沒有併發收集G1可以幫助你在這裏,因爲總是會有併發模式失敗,回退到完全的GC。 (默認在Java 7中)ParallelGC收集器也將提供較差的性能,因爲它隱式使用-XX:+UseParallelOldGC並且遍歷(用於標記)單個LinkedList不適合並行化。

我剛剛發佈a blog article顯示(在圖8中,左側)一個微型基準測試,它與您的測試非常相似。在這種情況下,ParNewGC大幅跑贏其他收藏家。

問候, 安德烈亞斯

+0

我忘了說我的建議可能會幫助你提高樣本的吞吐量,但我不知道用Oracle的Hotspot JVM和這個樣本保持100ms範圍內暫停的方法。恕我直言,暫停將永遠在6秒的堆和一個殘酷的GC樣本的秒(數十秒範圍內)。 –