尋找垃圾對象的算法:1. 引用計數(沒法處理循環引用) 2. 根尋法(被普遍引用在gc算法中)算法
清理垃圾的算法: 1. 標記複製 2. 標記清理 3. 標記整理數據結構
分代算法的好處:併發
1. 分代處理,能夠減小一次處理的內存大小,減小停頓時間。 jvm
2. 不一樣的代有不一樣的特色,再加上有針對性的gc算法和代碼優化,總體的性能更好:性能
年輕代特色是分配快,對象存活時間短,因此採用標記複製時間消耗不會過高,效率高。優化手段就是減小分配需求的速度,減小對象的存活時間,這樣使得youngGc頻率和停頓都下降,也減小了老年代的壓力,是頗有效的優化手段。優化
老年代的特色是內存較大,對象存活時間久,因此相對來講比較年輕代要複雜。因此有各類各樣不一樣的gc算法來對老年代進行處理,考量點主要是吞吐量和停頓時間。spa
老年代擔任的一個很重要的角色是給youngGC作擔保,保證對象的晉升能夠成功。因此這裏的一個重要的指標是,FullGC發生的頻率。FullGC的發生,通常是因爲老年代的內存清理速度更不上晉升的速度,或者是老年代的內存碎片,因此保證對象的存活時間儘量的短是有不少好處的。線程
GC算法: 日誌
1. 串行GC對象
2. ThroughPutGC(並行GC)—目的:提升吞吐量
3. ConcurrentGC— 目的:下降停頓
垃圾收集器:
Serial收集器:
年輕代的收集器+串行GC+標記複製
ParNew收集器:
年輕代的收集器+並行GC+標記複製
Parallel Scavenge收集器:
年輕代的收集器+並行GC+標記複製。 和ParNew的區別多是引進了用戶設定目標,gc本身調整大小的機制
Serial Old收集器:
老年代的收集器+串行GC+標記整理
Parallel Old收集器:
老年代的收集器+並行GC+標記整理
CMS收集器:
老年代的收集器+併發GC+標記清理
G1收集器:
年輕代+老年代的收集器
年輕代=並行GC+標記複製
老年代=併發GC+標記整理
GC算法選擇:
Gc算法的選擇考慮的方向主要是停頓時間和吞吐量倆個方面。須要根據程序的特徵,CPU資源來進行權衡。
1. 先從吞吐量來講,其實考慮的主要是可用的cpu資源有多少(即有多少空閒的cpu資源)。若是空閒的cpu資源較少,使用throughput類的gc會更好,由於它自己的收集效率就比並發的gc好。而併發的gc算法會和應用程序搶奪cpu資源。就會形成吞吐量降低。而若是空閒的cpu資源不少,那麼併發的gc能夠利用這些空閒的cpu來併發的完成gc,並且對應用程序的影響是很小的。反正那些cpu空着也是空着。若是這時使用throughput收集器,這些空閒的cpu會被白白浪費,並且還會stw來獨佔cpu進行gc,若是gc自己沒法徹底利用這些cpu資源,並且還會影響應用程序,就得不償失了。
2. 在衡量停頓時間上,通常來講,throughput的收集器的90%的響應時間都要比並發gc的快,由於90%的時間是沒有gc的,只有在發生full-gc時纔會致使響應時間過長。 可是,當full-gc發生的更頻繁的時候,這個百分比也就會降低。固然,當發生這種狀況是,併發gc也許也會難以免full-gc。
CMS和G1的選擇:
通常認爲,當堆比較大時(大於4G),使用G1比較好,不然CMS的性能會更好一點。緣由在於,gc消耗的時間和堆的大小是有很大的關係的,好比標記須要掃描整個堆。可是G1對堆進行了進一步的劃分,在清理週期中,能夠只清理垃圾最多的一部分分區,因此在大堆的狀況下,性能是比CMS要好一些。另外,因爲G1是標記複製的算法,因此不容易形成內存碎片。
反過來,因爲G1的算法和數據結構要更加複雜,因此它的內存和cpu的消耗也就更大。若是堆自己就比較小的話,使用G1會形成應用程序可使用的堆大小會更小。
GC調優基礎:
1. 調整堆的大小:
A.堆太小,致使gc頻率變高。 堆太大,致使單次gc的時間變長。因此,要經過調整對的大小來找到一個咱們認爲相對合適的目標。目標主要從停頓時間和吞吐量倆個方面來考慮。
B.堆的大小,不要超過機器的物理內存,由於jvm對底層的swap並沒有感知,swap帶來的gc停頓變長,可能會致使併發失效,從而致使full-gc
C.-Xms4096m -Xmx4096m 這種最大和初始同樣時,堆的大小就不會由於自適應調整而改變。當咱們知道最合適的堆的大小時,這種設置是有好處的,由於jvm不須要再進行堆的大小計算和調整。可是當咱們不知道合適的堆的大小應該是多少時,我以爲仍是不要這麼搞吧。
2. 調整代的大小:
新生代過小,會致使youngGc頻率高,對象晉升快,對老年代的壓力增大。 新生代太大,新生代的gc時間可能會變長,對象晉升變慢,老年代的大小受限,可能會致使full-gc。
老年代過小,可能會致使full-gc變多。老年代太大,可能會致使老年代的gc太慢。
-XX:NewRatio=N
-XX:NewSize=N
-XmnN
-XX:MaxNewSize=N
3. 調整永久代和元空間的大小:
永久代和元空間也會觸發fullgc,因此也須要關注一下大小是否合適。
4. 控制併發:
gc的線程任務是計算密集型
-XX:ParallelGCThreads=N 來控制並並GC的線程數,好比:
a.使用-XX:+UseParallelGC收集新生代空間
b.使用-XX:+UseParallelOldGC收集老年代空間
c. CMS的「STW」階段(不包括Full-GC)
d. G1的「STW」階段(不包括Full-GC)
e.使用-XX:+UseParNewGC收集的新生代空間
f.使用-XX:+UseG1GC收集的新生代空間
5. 自適應調整:
用戶設定目標:響應時間和gc消耗時間佔比 -XX:MaxGCPauseMillis=N 和 -XX:GCTimeRatio=N ,這倆個配置會同時影響年輕代和老年代
自適應調整會調整堆的大小,新生代和老年代的大小來儘可能適用目標值。若是堆的初始值和最大值同樣,則自適應調整不會對堆的總體大小進行調整。若是堆,年輕代的初始值和最大值同樣,則自適應就徹底失效了,不過survivor仍是會進行自適應調整。
-XX:-UseAdaptiveSizePolicy 關閉自適應(默認是開)。 -XX:+PrintAdaptiveSizePolicy 在gc時會打印調整的信息。
對精細化計算過的jvm內存比例,能夠把各個區域的大小進行限定,這樣能夠減小堆自適應的消耗。
Throughput收集器:
Gc分爲youngGc和fullGC。正常的FullGc不會對永久區進行回收,當回收區滿時,會觸發fullGc,這時會對永久區進行回收。
調優:
1.自適應調整:首先會調整堆的大小來達到目標停頓時間,而後慢慢增大堆的大小來儘量達到目標吞吐量。而後又開始下降堆的大小來減小jvm的內存佔用。目標值的設定必定要合理,太極端會致使極端的問題。
2.調整並行線程數,由於是stw,並且gc的任務是計算密集型的,因此線程數根據cpu和計算密集型的公式就好。
CMS:
CMS有三個gc動做:1. yooung區的gc(STW) 2.老年代的gc(併發gc+標記清除) 3.full-gc(串行full-gc)
老年代的GC過程:
1. 初始標記(STW): 標記那些直接被Root對象引用的對象
2.併發標記: 併發的進行Root Tracing的過程,時間較長。沒法處理浮動垃圾。
3.從新標記(STW): 主要是對步驟2中可能出現的遺漏進行補充,時間比1長,可是遠比2短
4.併發清理:併發的進行垃圾的清理,不會進行內存壓縮,因此可能會形成內存碎片
一 YGC 89.853: [GC 89.853: [ParNew: 629120K->69888K(629120K), 0.1218970 secs]
三併發標記 90.059: [CMS-concurrent-mark-start]
90.887: [CMS-concurrent-mark: 0.823/0.828 secs]
[Times: user=1.11 sys=0.00, real=0.83 secs]
四預清理 90.887: [CMS-concurrent-preclean-start]
90.892: [CMS-concurrent-preclean: 0.005/0.005 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
五從新標記 90.892: [CMS-concurrent-abortable-preclean-start]
92.392: [GC 92.393: [ParNew: 629120K->69888K(629120K), 0.1289040 secs]
1331374K->803967K(2027264K), 0.1290200 secs]
[Times: user=0.44 sys=0.01, real=0.12 secs]
94.473: [CMS-concurrent-abortable-preclean: 3.451/3.581 secs]
[Times: user=5.03 sys=0.03, real=3.58 secs]
94.474: [GC[YG occupancy: 466937 K (629120 K)]
94.474: [Rescan (parallel) , 0.1850000 secs]
94.659: [weak refs processing, 0.0000370 secs]
94.659: [scrub string table, 0.0011530 secs]
[1 CMS-remark: 734079K(1398144K)]
1201017K(2027264K), 0.1863430 secs]
[Times: user=0.60 sys=0.01, real=0.18 secs]
這個過程執行了多個步驟,首先是可中斷的預清理。因爲jvm爲了不倆次連續的停頓(剛發生一次YGC後,立馬發生從新標記),因此在發生YGC後,jvm預測下一個YGC發生的時間週期,在這個週期中間中止了可停頓預清理,而後開始了從新標記。
這也意味了,老年代的併發週期和YGC是併發進行的。
六併發清理階段 94.661: [CMS-concurrent-sweep-start]
95.223: [GC 95.223: [ParNew: 629120K->69888K(629120K), 0.1322530 secs]
999428K->472094K(2027264K), 0.1323690 secs]
[Times: user=0.43 sys=0.00, real=0.13 secs]
95.474: [CMS-concurrent-sweep: 0.680/0.813 secs]
[Times: user=1.45 sys=0.00, real=0.82 secs]
這裏日誌表示,在併發清理階段,發送了YGC,也代表老年代的併發週期和YGC是併發進行的。並且在併發週期中至少會發生一次YGC,就是可中斷預清理的過程當中。
七併發重置階段 95.474: [CMS-concurrent-reset-start]
95.479: [CMS-concurrent-reset: 0.005/0.005 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
這是併發週期的最後一個階段,可是咱們無法中日誌中得知這個階段回收了多少內存,只能從上下的GC日誌中進行推測。
八併發模式失敗(concurrent mode failure)
267.006: [GC 267.006: [ParNew: 629120K->629120K(629120K), 0.0000200 secs]
267.006: [CMS267.350: [CMS-concurrent-mark: 2.683/2.804 secs]
[Times: user=4.81 sys=0.02, real=2.80 secs]
(concurrent mode failure):
1378132K->1366755K(1398144K), 5.6213320 secs]
2007252K->1366755K(2027264K),
[CMS Perm : 57231K->57222K(95548K)], 5.6215150 secs]
[Times: user=5.63 sys=0.00, real=5.62 secs]
這是在發生YGC時,老年代沒有足夠的空間來容納晉升的對象,就退化成了FULL-GC。這個過程是串行的,因此很慢。
九晉升失敗 6043.903: [GC 6043.903:
[ParNew (promotion failed): 614254K->629120K(629120K), 0.1619839 secs]
6044.217: [CMS: 1342523K->1336533K(2027264K), 30.7884210 secs]
2004251K->1336533K(1398144K),
[CMS Perm : 57231K->57231K(95548K)], 28.1361340 secs]
[Times: user=28.13 sys=0.38, real=28.13 secs]
這是在發生YGC後,並且JVM判斷old區有足夠的空間容納晉升對象,可是在晉升的過程當中,因爲old區的內存碎片,致使的晉升失敗,而後進行了FULL-GC. 因爲這個過程是發送YGC失敗致使的,因此耗時比並發模式失敗還要多。
十永久代或者元空間用盡致使的full-gc
279.803: [Full GC 279.803:
[CMS: 88569K->68870K(1398144K), 0.6714090 secs]
558070K->68870K(2027264K),
[CMS Perm : 81919K->77654K(81920K)],
0.6716570 secs]
G1: G1對堆內存進所有行了分區(region),一個代的空間裏的分區並非連續的。有些分區屬於老年代,有些分區屬於年輕代。年輕代的gc和其餘回收器是同樣的,只因此也進行分區是由於能夠更方便的進行分區的大小調整。老年代的gc是對垃圾最多的分區進行清理,這樣可使用更少的時間來達到最好的清理效果。因此對於比較大的堆,G1的停頓時間是要比CMS好的。 G1的老年代清理是把一個region
G1的4個操做:
1.新生代垃圾的收集 :並行收集
2.後臺收集,併發週期:對垃圾最多的分區進行標記,期間至少會發生一次YGC
3.混合式垃圾收集
4.必要的full-gc
併發週期:
a. 50.541: [GC pause (young) (initial-mark), 0.27767100 secs]
[Eden: 1220M(1220M)->0B(1220M)
Survivors: 144M->144M Heap: 3242M(4096M)->2093M(4096M)]
[Times: user=1.02 sys=0.04, real=0.28 secs]
併發週期的開始—初始化標記。這個動做是STW的,這裏使用了YGC來進行STW,
b. 50.819: [GC concurrent-root-region-scan-start]
51.408: [GC concurrent-root-region-scan-end, 0.5890230]
掃描根分區,這個過程是併發進行的。可是,這個階段中,不能進行YGC,若是這個時候觸發了YGC,YGC必須等待這個動做的結束,而後再進行。這致使了YGC時間變長,意味着須要進行調優了,因此就會有下面這種日誌。
350.994: [GC pause (young)
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],
0.37559600 secs]
c. 350.994: [GC pause (young)
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],
0.37559600 secs]
併發標記,這個過程是能夠中斷的,中間是能夠有YGC併發進行的。
d. 120.910: [GC remark 120.959:
[GC ref-PRC, 0.0000890 secs], 0.0718990 secs]
[Times: user=0.23 sys=0.01, real=0.08 secs]
120.985: [GC cleanup 3510M->3434M(4096M), 0.0111040 secs]
[Times: user=0.04 sys=0.00, real=0.01 secs]
從新標記和清理階段。這倆個階段是STW的,並且這個清理階段只會清理不多的垃圾,主要仍是對垃圾的標記。
e. 120.996: [GC concurrent-cleanup-start]
120.996: [GC concurrent-cleanup-end, 0.0004520]
一個併發清理,一樣回收的垃圾不多。
混合式垃圾收集週期:
這個週期會進行YGC和老年代的併發清理。這是stw的
a. 79.826: [GC pause (mixed), 0.26161600 secs]
....
[Eden: 1222M(1222M)->0B(1220M)
Survivors: 142M->144M Heap: 3200M(4096M)->1964M(4096M)]
[Times: user=1.01 sys=0.00, real=0.26 secs]
混合式垃圾回收週期會進行多輪,直到知足咱們設定的指標。而後整個老年代的回收週期就結束了。
FULL-GC的發生:
1. 併發模式失敗:老年代開始了標記週期,可是在標記週期完成以前就被填滿了。這種狀況下,G1日誌裏會有放棄標記週期的日誌:
51.408: [GC concurrent-mark-start]
65.473: [Full GC 4095M->1395M(4096M), 6.1963770 secs]
[Times: user=7.87 sys=0.00, real=6.20 secs]
71.669: [GC concurrent-mark-abort]
2. 晉升失敗:老年代已經開始了mix-gc週期,可是老年代在垃圾回收釋放出足夠的內存以前就被耗盡了。這種狀況是mix-gc以後立刻就有一次Full-Gc
2226.224: [GC pause (mixed)
2226.440: [SoftReference, 0 refs, 0.0000060 secs]
2226.441: [WeakReference, 0 refs, 0.0000020 secs]
2226.441: [FinalReference, 0 refs, 0.0000010 secs]
2226.441: [PhantomReference, 0 refs, 0.0000010 secs]
2226.441: [JNI Weak Reference, 0.0000030 secs]
(to-space exhausted), 0.2390040 secs]
....
[Eden: 0.0B(400.0M)->0.0B(400.0M)
Survivors: 0.0B->0.0B Heap: 2006.4M(2048.0M)->2006.4M(2048.0M)]
[Times: user=1.70 sys=0.04, real=0.26 secs]
2226.510: [Full GC (Allocation Failure)
2227.519: [SoftReference, 4329 refs, 0.0005520 secs]
2227.520: [WeakReference, 12646 refs, 0.0010510 secs]
2227.521: [FinalReference, 7538 refs, 0.0005660 secs]
2227.521: [PhantomReference, 168 refs, 0.0000120 secs]
2227.521: [JNI Weak Reference, 0.0000020 secs]
2006M->907M(2048M), 4.1615450 secs]
[Times: user=6.76 sys=0.01, real=4.16 secs]
3.疏散失敗:在新生代的回收中,survivor區和老年代沒有足夠的內存來容納晉升和存活的對象,這時會有一個比較特別的日誌:
60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]
4.巨型對象分配失敗:沒有什麼好的辦法來排查這種問題,若是出現了比較奇怪的full-gc,能夠考慮這種狀況。