[譯]Java8官方GC調優指南 --(三) 分代

本套文章是Java8官方GC調優指南的全文翻譯,點擊查看原文,原文章名稱《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》html

3 Generations 分代

簡介

Java SE Platform的一個優點就是它讓開發者無需瞭解內存分配就能上手開發(其實徹底不懂也不行)。 可是,當垃圾回收器已經成爲Java程序的主要瓶頸時,瞭解其內部實現是很是有用的。java

垃圾回收器會處理那些已經沒有任何指針指向的對象。最簡單的垃圾回收算法迭代全部可達的對象。剩餘不可達的對象就是可回收對象。GC時間主要取決於存活對象的數量,若是是大型服務端應用,存活對象超多,最好別用這種回收器。web

JVM使用分代回收策略(generational colletion),這種策略包括了許多不一樣的垃圾回收算法。 最原始的垃圾回收檢查heap中的全部存活對象,分代回收策略開發多個假設和觀察屬性去減小回收對象的工做量。 這些觀察屬性中最重要的就是弱代猜測,這個猜測標記了大多數對象只存活一小段時間。算法

下圖中藍色區域是存活的對象的典型分佈。X軸是存活的對象數量,Y軸是對象使用的總字節數。圖中左側的垂直向下的 peak表明那些分配很快便可回收的對象。例如,for循環中建立的對象,生命週期就是那一次循環,循環結束對象就能夠被收集了緩存

3-1-typical-distruibution-for-lifetimes-of-objects

有些對象卻壽命久的多,因此分佈圖一直平滑延伸到了右側。這些對象從服務啓動以後就分配了,直到整個進程退出。對於一些Java程序來講,它的對象存活分佈圖可能跟上圖是徹底不一樣的。不過很使人驚訝的是不少應用的分佈圖都和這個圖相差無幾。專一於回收那些「存活時間短的對象」的回收策略纔是最有效的bash

要讓這個場景更加樂觀,內存也要分代管理的。當某個代的內存滿了,這個代就開始垃圾回收。大量的對象都是短命對象。當young generation滿了,就觸發一次minor collection。其餘代的垃圾對象不會被回收。minor collection的最理想狀況是,假定全部短命對象都在young區,那就直接回收了。minor collection的回收成本在於存活對象佔總對象的比例;若是young區的對象全死了,那麼回收起來會很快。通常狀況下,存活對象中的一部分在每次 minor gc後會移動到tenured generation。最終,tenured generation 會被裝滿也就是整個heap都滿了,觸發major collection。Major collection一般會持續更久的時間,由於大量的對象都要被參與回收。服務器

在Ergonomics章節提到,jvm會爲不一樣的Java應用選擇垃圾回收器動態的提供不錯的性能。serial garbage collector和它的默認參數是給那些小型Java應用設計的。parallel 或者吞吐量 garbage collector是用來給中型應用設計的。heap size參數和一些附加的特性參數是爲了給服務器級別的應用設計的。這些擦書大多數都工做的很好,但也不必定。(這不是廢話麼,之前聽某廠大牛講課說JVM原生的回收器就是垃圾,不過通常小廠也沒那麼大的堆。Hey Garbage Collector,why don't you collect yourself?) 下面是這個文檔的中心準則:網絡

若是垃圾回收器成爲了系統瓶頸,你最好自定義每一個分代的heap size。檢查gc日誌來尋找屬於你的最佳參數。oracle

筆者認爲上面這句話詮釋了GC調優的本質,也就是說,調優GC參數,須要根據程序的特色來調整堆大小,由於堆的大小必定程度決定了GC的停頓時間,還要了解全部可選的JVM性能參數,結合本身程序運行的物理環境,調整參數,觀察GC日誌來確保吞吐量和停頓時間符合要求。如何才能稱這兩個目標都符合要求呢?如何制定目標?這個就要看本身了,咱們後面再詳細說,先把本套文檔看完。jvm

下圖表示了默認的分代策略(對於全部的回收器的使用,除了parallel collector和G1)

3-2-default-arrangement-of-generations-except-for-parallel-collector-and-g1

Java應用初始化時,最大的地址空間只是預約了一下可是並無真實分配。完整的內存地址空間能夠被分爲young區和tenured區。

young區包含1個eden區和兩個suvivor區。大多數object都在eden區分配。任意時刻都有一個survivor區是空的,eden區存活對象GC後會轉移到這個survivor區;另外一個survivor區是給下次複製收集來用的。對象會在兩個s區來回複製,直到它們年齡足夠老,能夠晉升到tenured區。

Performance Considerations 性能因素

對於垃圾回收器的性能,有兩個主要的測量方式:

  • 吞吐量,除GC消耗時間以外的應用運行時間百分比。吞吐量包含內存分配時間。
  • 停頓次數,GC觸發STW時程序是無響應的。

萌新提示:只要觸發了STW,程序就會暫停,必須等GC結束才能繼續響應。

用戶對於垃圾回收器有不一樣的需求。例如,某個策略:一個web服務最佳的優化目標是吞吐量優先,由於GC回收形成的停頓可能會被網絡延遲掩蓋。不過,在一個圖形化程序中,即便是短暫的停頓也會影響用戶體驗。

還有一些用戶對些其餘的因素比較敏感。Footprint是一個進程的內存佔用狀況,經過內存頁和緩存行來度量。在給定物理內存和CPU核數的操做系統上,Footprint決定了應用的可伸縮性。

Promptness(敏捷性)是分佈式系統很是重要的一個性能因素,包括RMI,指的是對象死亡和內存可用之間的間隔時間。

一般,給特定的分代指定大小是在這些因素之間作權衡。例如,一個很是大的young區可能會提升吞吐量,可是也會提升footprint,promptness和停頓時間。young區停頓時間能夠經過調小young區大小來下降。一個分代的大小不會影響其餘分代的回收頻率和停頓時間。

沒有一個簡單正確的方式來決定一個分代的大小。最好的選擇來自於應用使用內存的方式和用戶的需求。JVM的默認回收器和參數不必定是最優的,你可能須要經過參數調整來獲取最佳的效果。具體能夠查看章節Sizing the Generations。

Measurement 度量

使用特定於應用程序的指標能夠很好的度量Throughput(吞吐量) 和 Footprint(內存佔用)。例如,一個web服務的吞吐量能夠經過客戶端壓力測試來獲取,服務器的footprint能夠經過pmap命令來查看。GC停頓信息也能夠經過JVM輸出日誌來查看。

參數 -verbose:gc能夠在每次GC後輸出heap和垃圾回收的信息。例如,這是一條服務器的GC日誌:

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
複製代碼

日誌標明兩次minor collection和一次major collection。箭頭左右的兩個數字(325407K->83000K)表示gc先後的內存空間。在minor collection以後,這個空間中有一些垃圾對象,可是內存空間並無釋放。這些對象要麼在tenured generation,要麼就是被tenured generation的對象引用了。

圓括號中的數字,776768K是已經提交的heap大小:操做系統已經分配給Java建立對象的空間大小。注意這個數字值包含一個survivor區的大小。除了垃圾回收自己,只有一個survivor區會被用來儲存對象。

最後一項,0.2300771 secs表示執行GC花費的時間。

第三行的Full GC日誌跟上面的Young GC差很少。

注意,-verbose:gc輸出的日誌格式在未來的release版本中可能會更改

-XX:+PrintGCDetails參數會多打些日誌。下面是例子:

[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]
複製代碼

這代表minor GC回收了young區98%的空間,DefNew: 64575K->959K(64576K)執行消耗了0.0457646 secs

所有heap的使用率下降到了大約51%左右(196016K->133633K(261184K)),總時間會比minor gc時間稍微高一點。

注意,-XX:+PrintGCDetails輸出的日誌格式在未來的release版本中可能會更改

-XX:+PrintGCTimeStamps參數爲每次GC增長了一個時間戳。這個在觀察gc頻率的時候會很是有用。

111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]
複製代碼

此次回收發生在Java程序運行至111秒時。minor gc差很少在同一時刻開始執行。更多的,這個log還顯示了Tenured gc。tenured generation的使用率減小到了10%(18154K->2311K(24576K))),花費了0.1293306 secs,大約130毫秒。

相關文章
相關標籤/搜索