GC調(diào)優(yōu)實(shí)戰(zhàn)之高分配速率High?Allocation?Rate
高分配速率(High Allocation Rate)
分配速率(Allocation rate)表示單位時(shí)間內(nèi)分配的內(nèi)存量。通常使用 MB/sec作為單位, 也可以使用 PB/year 等。
分配速率過高就會(huì)嚴(yán)重影響程序的性能。在JVM中會(huì)導(dǎo)致巨大的GC開銷。
如何測量分配速率?
指定JVM參數(shù): -XX:+PrintGCDetails -XX:+PrintGCTimeStamps , 通過GC日志來計(jì)算分配速率. GC日志如下所示:
0.291: [GC (Allocation Failure) [PSYoungGen: 33280K->5088K(38400K)] 33280K->24360K(125952K), 0.0365286 secs] [Times: user=0.11 sys=0.02, real=0.04 secs] 0.446: [GC (Allocation Failure) [PSYoungGen: 38368K->5120K(71680K)] 57640K->46240K(159232K), 0.0456796 secs] [Times: user=0.15 sys=0.02, real=0.04 secs] 0.829: [GC (Allocation Failure) [PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(159232K), 0.0861795 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
計(jì)算 上一次垃圾收集之后,與下一次GC開始之前的年輕代使用量, 兩者的差值除以時(shí)間,就是分配速率。 通過上面的日志, 可以計(jì)算出以下信息:
- JVM啟動(dòng)之后
291ms, 共創(chuàng)建了33,280 KB的對(duì)象。 第一次 Minor GC(小型GC) 完成后, 年輕代中還有5,088 KB的對(duì)象存活。 - 在啟動(dòng)之后
446 ms, 年輕代的使用量增加到38,368 KB, 觸發(fā)第二次GC, 完成后年輕代的使用量減少到5,120 KB。 - 在啟動(dòng)之后
829 ms, 年輕代的使用量為71,680 KB, GC后變?yōu)?nbsp;5,120 KB。
可以通過年輕代的使用量來計(jì)算分配速率, 如下表所示:
| Event | Time | Young before | Young after | Allocated during | Allocation rate |
|---|---|---|---|---|---|
| 1st GC | 291ms | 33,280KB | 5,088KB | 33,280KB | 114MB/sec |
| 2nd GC | 446ms | 38,368KB | 5,120KB | 33,280KB | 215MB/sec |
| 3rd GC | 829ms | 71,680KB | 5,120KB | 66,560KB | 174MB/sec |
| Total | 829ms | N/A | N/A | 133,120KB | 161MB/sec |
通過這些信息可以知道, 在測量期間, 該程序的內(nèi)存分配速率為 161 MB/sec。
分配速率的意義
分配速率的變化,會(huì)增加或降低GC暫停的頻率, 從而影響吞吐量。 但只有年輕代的 minor GC 受分配速率的影響, 老年代GC的頻率和持續(xù)時(shí)間不受分配速率(allocation rate)的直接影響, 而是受到 提升速率(promotion rate)的影響, 請(qǐng)參見下文。
現(xiàn)在我們只關(guān)心 Minor GC 暫停, 查看年輕代的3個(gè)內(nèi)存池。因?yàn)閷?duì)象在 Eden區(qū)分配, 所以我們一起來看 Eden 區(qū)的大小和分配速率的關(guān)系. 看看增加 Eden 區(qū)的容量, 能不能減少 Minor GC 暫停次數(shù), 從而使程序能夠維持更高的分配速率。
經(jīng)過我們的實(shí)驗(yàn), 通過參數(shù) -XX:NewSize、 -XX:MaxNewSize 以及 -XX:SurvivorRatio 設(shè)置不同的 Eden 空間, 運(yùn)行同一程序時(shí), 可以發(fā)現(xiàn):
- Eden 空間為
100 MB時(shí), 分配速率低于100 MB/秒。 - 將 Eden 區(qū)增大為
1 GB, 分配速率也隨之增長,大約等于200 MB/秒。
為什么會(huì)這樣? —— 因?yàn)闇p少GC暫停,就等價(jià)于減少了任務(wù)線程的停頓,就可以做更多工作, 也就創(chuàng)建了更多對(duì)象, 所以對(duì)同一應(yīng)用來說, 分配速率越高越好。
在得出 “Eden區(qū)越大越好” 這個(gè)結(jié)論前, 我們注意到, 分配速率可能會(huì),也可能不會(huì)影響程序的實(shí)際吞吐量。 吞吐量和分配速率有一定關(guān)系, 因?yàn)榉峙渌俾蕰?huì)影響 minor GC 暫停, 但對(duì)于總體吞吐量的影響, 還要考慮 Major GC(大型GC)暫停, 而且吞吐量的單位不是 MB/秒, 而是系統(tǒng)所處理的業(yè)務(wù)量。
示例
參考 Demo程序。假設(shè)系統(tǒng)連接了一個(gè)外部的數(shù)字傳感器。應(yīng)用通過專有線程, 不斷地獲取傳感器的值,(此處使用隨機(jī)數(shù)模擬), 其他線程會(huì)調(diào)用 processSensorValue() 方法, 傳入傳感器的值來執(zhí)行某些操作:
public class BoxingFailure {
private static volatile Double sensorValue;
private static void readSensor() {
while(true) sensorValue = Math.random();
}
private static void processSensorValue(Double value) {
if(value != null) {
//...
}
}
}如同類名所示, 這個(gè)Demo是模擬 boxing 的。為了 null 值判斷, 使用的是包裝類型 Double。 程序基于傳感器的最新值進(jìn)行計(jì)算, 但從傳感器取值是一個(gè)重量級(jí)操作, 所以采用了異步方式: 一個(gè)線程不斷獲取新值, 計(jì)算線程則直接使用暫存的最新值, 從而避免同步等待。
Demo 程序在運(yùn)行的過程中, 由于分配速率太大而受到GC的影響。下一節(jié)將確認(rèn)問題, 并給出解決辦法。
高分配速率對(duì)JVM的影響
首先,我們應(yīng)該檢查程序的吞吐量是否降低。如果創(chuàng)建了過多的臨時(shí)對(duì)象, minor GC的次數(shù)就會(huì)增加。如果并發(fā)較大, 則GC可能會(huì)嚴(yán)重影響吞吐量。
遇到這種情況時(shí), GC日志將會(huì)像下面這樣,當(dāng)然這是上面的示例程序 產(chǎn)生的GC日志。 JVM啟動(dòng)參數(shù)為 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx32m:
2.808: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003076 secs] 2.819: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003079 secs] 2.830: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0002968 secs] 2.842: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003374 secs] 2.853: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0004672 secs] 2.864: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003371 secs] 2.875: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003214 secs] 2.886: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003374 secs] 2.896: [GC (Allocation Failure) [PSYoungGen: 9760K->32K(10240K)], 0.0003588 secs]
很顯然 minor GC 的頻率太高了。這說明創(chuàng)建了大量的對(duì)象。另外, 年輕代在 GC 之后的使用量又很低, 也沒有 full GC 發(fā)生。 種種跡象表明, GC對(duì)吞吐量造成了嚴(yán)重的影響。
解決方案
在某些情況下,只要增加年輕代的大小, 即可降低分配速率過高所造成的影響。增加年輕代空間并不會(huì)降低分配速率, 但是會(huì)減少GC的頻率。如果每次GC后只有少量對(duì)象存活, minor GC 的暫停時(shí)間就不會(huì)明顯增加。
運(yùn)行 示例程序 時(shí), 增加堆內(nèi)存大小,(同時(shí)也就增大了年輕代的大小), 使用的JVM參數(shù)為 -Xmx64m:
2.808: [GC (Allocation Failure) [PSYoungGen: 20512K->32K(20992K)], 0.0003748 secs] 2.831: [GC (Allocation Failure) [PSYoungGen: 20512K->32K(20992K)], 0.0004538 secs] 2.855: [GC (Allocation Failure) [PSYoungGen: 20512K->32K(20992K)], 0.0003355 secs] 2.879: [GC (Allocation Failure) [PSYoungGen: 20512K->32K(20992K)], 0.0005592 secs]
但有時(shí)候增加堆內(nèi)存的大小,并不能解決問題。通過前面學(xué)到的知識(shí), 我們可以通過分配分析器找出大部分垃圾產(chǎn)生的位置。實(shí)際上在此示例中, 99%的對(duì)象屬于 Double 包裝類, 在readSensor 方法中創(chuàng)建。最簡單的優(yōu)化, 將創(chuàng)建的 Double 對(duì)象替換為原生類型 double, 而針對(duì) null 值的檢測, 可以使用 Double.NaN 來進(jìn)行。由于原生類型不算是對(duì)象, 也就不會(huì)產(chǎn)生垃圾, 導(dǎo)致GC事件。優(yōu)化之后, 不在堆中分配新對(duì)象, 而是直接覆蓋一個(gè)屬性域即可。
對(duì)示例程序進(jìn)行簡單的改造( 查看diff ) 后, GC暫停基本上完全消除。有時(shí)候 JVM 也很智能, 會(huì)使用 逃逸分析技術(shù)(escape ****ysis technique) 來避免過度分配。簡單來說,JIT編譯器可以通過分析得知, 方法創(chuàng)建的某些對(duì)象永遠(yuǎn)都不會(huì)“逃出”此方法的作用域。這時(shí)候就不需要在堆上分配這些對(duì)象, 也就不會(huì)產(chǎn)生垃圾, 所以JIT編譯器的一種優(yōu)化手段就是: 消除內(nèi)存分配。
以上就是GC調(diào)優(yōu)實(shí)戰(zhàn)之高分配速率High Allocation Rate的詳細(xì)內(nèi)容,更多關(guān)于GC調(diào)優(yōu)高分配速率的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
原文鏈接:https://plumbr.io/handbook/gc-tuning-in-practice
相關(guān)文章
Java 實(shí)戰(zhàn)項(xiàng)目錘煉之仿天貓網(wǎng)上商城的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+servlet+mysql+ajax實(shí)現(xiàn)一個(gè)仿天貓網(wǎng)上商城項(xiàng)目,大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
idea如何為java程序添加啟動(dòng)參數(shù)
文章介紹了如何在Java程序中添加啟動(dòng)參數(shù),包括program arguments、VM arguments和Environment variables,并解釋了如何在代碼中使用System類獲取這些參數(shù)2025-01-01
淺談什么是SpringBoot異常處理自動(dòng)配置的原理
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著SpringBoot異常處理自動(dòng)配置展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
java 設(shè)計(jì)模式之單例的實(shí)例詳解
這篇文章主要介紹了java 設(shè)計(jì)模式之單例的實(shí)例詳解的相關(guān)資料, 希望通過本文能幫助到大家,讓大家徹底理解掌握單例模式,需要的朋友可以參考下2017-09-09

