Java記憶體回收調優教程及執行個體

來源:互聯網
上載者:User

Java 記憶體回收調優和其它效能最佳化活動相比,首先你要確保自己足夠瞭解整個應用的情況以及調優預期的結果,而不是單單滿足於應用的某一部分調優。一般情況下,遵循以下過程比較容易:

    明確自己的效能目標。
    測試。
    測量調優結果。
    與目標進行比較。
    改變方法並再次測試。

效能調優目標要是可確定且可測量的,這非常重要。這些目標包括延遲、輸送量和容量,想要瞭解更多,我推薦看看記憶體回收手冊(Garbage Collection Handbook)中相應的章節。讓我們看看在實踐中如何設定並達到這樣的調優目標。為了這個目的,讓我們來看一個範例程式碼:

//imports skipped for brevity
public class Producer implements Runnable {
 
  private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
 
  private Deque<byte[]> deque;
  private int objectSize;
  private int queueSize;
 
  public Producer(int objectSize, int ttl) {
    this.deque = new ArrayDeque<byte[]>();
    this.objectSize = objectSize;
    this.queueSize = ttl * 1000;
  }
 
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      deque.add(new byte[objectSize]);
      if (deque.size() > queueSize) {
        deque.poll();
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    executorService.scheduleAtFixedRate(new Producer(200 * 1024 * 1024 / 1000, 5), 0, 100, TimeUnit.MILLISECONDS);
    executorService.scheduleAtFixedRate(new Producer(50 * 1024 * 1024 / 1000, 120), 0, 100, TimeUnit.MILLISECONDS);
    TimeUnit.MINUTES.sleep(10);
    executorService.shutdownNow();
  }
}

代碼中提交了兩個作業(job),且每 100ms 運行一次。每個作業類比特定對象的生命週期:先建立對象,讓它們“存活”一段時間,然後忘記它們,讓 GC 回收記憶體。 運行這個樣本時,開啟 GC 日誌並使用以下參數:
1
    
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps

我們立即在記錄檔中看到 GC 的影響和下面這些相似:

2015-06-04T13:34:16.119-0200: 1.723: [GC (Allocation Failure) [PSYoungGen: 114016K->73191K(234496K)] 421540K->421269K(745984K), 0.0858176 secs] [Times: user=0.04 sys=0.06, real=0.09 secs]
2015-06-04T13:34:16.738-0200: 2.342: [GC (Allocation Failure) [PSYoungGen: 234462K->93677K(254976K)] 582540K->593275K(766464K), 0.2357086 secs] [Times: user=0.11 sys=0.14, real=0.24 secs]
2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics) [PSYoungGen: 93677K->70109K(254976K)] [ParOldGen: 499597K->511230K(761856K)] 593275K->581339K(1016832K), [Metaspace: 2936K->2936K(1056768K)], 0.0713174 secs] [Times: user=0.21 sys=0.02, real=0.07 secs]

基於日誌中的資訊,我們可以開始改善效能。並請牢記三個不同的目標:

    確保 GC pause(記憶體回收暫停)的最壞情況不要超過預期的臨界值。
    確保應用程式線程停滯時間不超過預先確定的閥值。
    降低基礎架構成本,同時確保我們仍可以實現合理的延遲和輸送量目標。

為此,以三個不同的配置各運行了10分鐘,在下表中總結了三個差距較大的結果:
堆     GC演算法     有效工作     長暫停
-Xmx12g     -XX:+UseConcMarkSweepGC     89.8%     560 ms
-Xmx12g     -XX:+UseParallelGC     91.5%     1,104 ms
-Xmx8g     -XX:+UseConcMarkSweepGC     66.3%     1,610 ms

實驗中,設定不同的 GC 演算法和不同的堆大小,運行相同的代碼,然後測量記憶體回收暫停期間和輸送量。實驗細節和結果的解釋都在我們的記憶體回收手冊中。看看手冊中的一些例子,修改一些簡單的配置造成延遲、輸送量等各方面的效能完全不同。

注意:為了保持樣本儘可能簡單,只有數量有限的輸入參數被改變,例如沒有對不同數量的核心(CPU core)或不同堆布局進行測試。




一次Java垃圾收集調優實戰


1 資料

    JDK5.0垃圾收集最佳化之--Don't Pause(花錢的年華)  
    編寫對GC友好,又不泄漏的代碼(花錢的年華)  
    JVM調優總結  
    JDK 6所有選項及預設值  

2 GC日誌列印

  GC調優是個很實驗很伽利略的活兒,GC日誌是先決的資料參考和最終驗證:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps(GC發生的時間) -XX:+PrintGCApplicationStoppedTime(GC消耗了多少時間) -XX:+PrintGCApplicationConcurrentTime(GC之間運行了多少時間)

 
3 收集器選擇


CMS收集器:暫停時間優先

   配置參數:-XX:+UseConcMarkSweepGC
   已預設無需配置的參數:-XX:+UseParNewGC(Parallel收集新生代) -XX:+CMSPermGenSweepingEnabled(CMS收集持久代) -XX:UseCMSCompactAtFullCollection(full gc時壓縮年老代)

   初始效果:1g堆記憶體的新生代約60m,minor gc約5-20毫秒,full gc約130毫秒。
Parallel收集器:輸送量優先

    配置參數: -XX:+UseParallelGC -XX:+UseParallelOldGC(Parallel收集年老代,從JDK6.0開始支援)

    已預設無需配置的參數: -XX:+UseAdaptiveSizePolicy(動態調整新生代大小)

    初始效果:1g堆記憶體的新生代約90-110m(動態調整),minor gc約5-20毫秒,full gc有無UseParallelOldGC 參數分別為1.3/1.1秒,差別不大。

    另外-XX:MaxGCPauseMillis=100 設定minor gc的期望最大時間,JVM會以此來調整新生代的大小,但在此測試環境中對象死的太快,此參數作用不大。
4 調優實戰

      Parallel收集高達1秒的暫停時間基本不可忍受,所以選擇CMS收集器。

      在被壓測的Mule 2.0應用裡,每秒都有大約400M的海量短命對象產生:

    因為預設60M的新生代太小了,頻繁發生minor gc,大約0.2秒就進行一次。
    因為CMS收集器中MaxTenuringThreshold(生代對象撐過過多少次minor gc才進入年老代的設定)預設0,存活的臨時對象不經過Survivor區直接進入年老代,不久就佔滿年老代發生full gc。

     對這兩個參數的調優,既要改善上面兩種情況,又要避免新生代過大,複製次數過多造成minor gc的暫停時間過長。

    使用-Xmn調到1/3 總記憶體。觀察後設定-Xmn500M,新生代實際約460m。(用-XX:NewRatio設定無效,只能用 -Xmn)。
    添加-XX:+PrintTenuringDistribution 參數觀察各個Age的對象總大小,觀察後設定-XX:MaxTenuringThreshold=5。

      最佳化後,大約1.1秒才發生一次minor gc,且速度依然保持在15-20ms之間。同時年老代的增長速度大大減緩,很久才發生一次full gc,

      參數定稿:

 -server -Xms1024m -Xmx1024m -Xmn500m -XX:+UseConcMarkSweepGC   -XX:MaxTenuringThreshold=5  -XX:+ExplicitGCInvokesConcurrent

 

最後服務處理速度從1180 tps 上升到1380 tps,調整兩個參數提升17%的效能還是筆很划算的買賣。


另外,JDK6 Update 7內建了一個VisualVM工具,內裡就是之前也有用過的Netbean Profiler,類似JConsole一樣使用,可以看到線程狀態,記憶體中對象以及方法的CPU時間等調優重要參考依據。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.