GC調優(yōu)實戰(zhàn)之高分配速率High?Allocation?Rate
高分配速率(High Allocation Rate)
分配速率(Allocation rate
)表示單位時間內分配的內存量。通常使用 MB/sec
作為單位, 也可以使用 PB/year
等。
分配速率過高就會嚴重影響程序的性能。在JVM中會導致巨大的GC開銷。
如何測量分配速率?
指定JVM參數(shù): -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
, 通過GC日志來計算分配速率. 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]
計算 上一次垃圾收集之后,與下一次GC開始之前的年輕代使用量, 兩者的差值除以時間,就是分配速率。 通過上面的日志, 可以計算出以下信息:
- JVM啟動之后
291ms
, 共創(chuàng)建了33,280 KB
的對象。 第一次 Minor GC(小型GC) 完成后, 年輕代中還有5,088 KB
的對象存活。 - 在啟動之后
446 ms
, 年輕代的使用量增加到38,368 KB
, 觸發(fā)第二次GC, 完成后年輕代的使用量減少到5,120 KB
。 - 在啟動之后
829 ms
, 年輕代的使用量為71,680 KB
, GC后變?yōu)?nbsp;5,120 KB
。
可以通過年輕代的使用量來計算分配速率, 如下表所示:
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 |
通過這些信息可以知道, 在測量期間, 該程序的內存分配速率為 161 MB/sec
。
分配速率的意義
分配速率的變化,會增加或降低GC暫停的頻率, 從而影響吞吐量。 但只有年輕代的 minor GC 受分配速率的影響, 老年代GC的頻率和持續(xù)時間不受分配速率(allocation rate
)的直接影響, 而是受到 提升速率(promotion rate
)的影響, 請參見下文。
現(xiàn)在我們只關心 Minor GC 暫停, 查看年輕代的3個內存池。因為對象在 Eden區(qū)分配, 所以我們一起來看 Eden 區(qū)的大小和分配速率的關系. 看看增加 Eden 區(qū)的容量, 能不能減少 Minor GC 暫停次數(shù), 從而使程序能夠維持更高的分配速率。
經過我們的實驗, 通過參數(shù) -XX:NewSize
、 -XX:MaxNewSize
以及 -XX:SurvivorRatio
設置不同的 Eden 空間, 運行同一程序時, 可以發(fā)現(xiàn):
- Eden 空間為
100 MB
時, 分配速率低于100 MB/秒
。 - 將 Eden 區(qū)增大為
1 GB
, 分配速率也隨之增長,大約等于200 MB/秒
。
為什么會這樣? —— 因為減少GC暫停,就等價于減少了任務線程的停頓,就可以做更多工作, 也就創(chuàng)建了更多對象, 所以對同一應用來說, 分配速率越高越好。
在得出 “Eden區(qū)越大越好” 這個結論前, 我們注意到, 分配速率可能會,也可能不會影響程序的實際吞吐量。 吞吐量和分配速率有一定關系, 因為分配速率會影響 minor GC 暫停, 但對于總體吞吐量的影響, 還要考慮 Major GC(大型GC)暫停, 而且吞吐量的單位不是 MB/秒
, 而是系統(tǒng)所處理的業(yè)務量。
示例
參考 Demo程序。假設系統(tǒng)連接了一個外部的數(shù)字傳感器。應用通過專有線程, 不斷地獲取傳感器的值,(此處使用隨機數(shù)模擬), 其他線程會調用 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) { //... } } }
如同類名所示, 這個Demo是模擬 boxing 的。為了 null 值判斷, 使用的是包裝類型 Double
。 程序基于傳感器的最新值進行計算, 但從傳感器取值是一個重量級操作, 所以采用了異步方式: 一個線程不斷獲取新值, 計算線程則直接使用暫存的最新值, 從而避免同步等待。
Demo 程序在運行的過程中, 由于分配速率太大而受到GC的影響。下一節(jié)將確認問題, 并給出解決辦法。
高分配速率對JVM的影響
首先,我們應該檢查程序的吞吐量是否降低。如果創(chuàng)建了過多的臨時對象, minor GC的次數(shù)就會增加。如果并發(fā)較大, 則GC可能會嚴重影響吞吐量。
遇到這種情況時, GC日志將會像下面這樣,當然這是上面的示例程序 產生的GC日志。 JVM啟動參數(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)建了大量的對象。另外, 年輕代在 GC 之后的使用量又很低, 也沒有 full GC 發(fā)生。 種種跡象表明, GC對吞吐量造成了嚴重的影響。
解決方案
在某些情況下,只要增加年輕代的大小, 即可降低分配速率過高所造成的影響。增加年輕代空間并不會降低分配速率, 但是會減少GC的頻率。如果每次GC后只有少量對象存活, minor GC 的暫停時間就不會明顯增加。
運行 示例程序 時, 增加堆內存大小,(同時也就增大了年輕代的大小), 使用的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]
但有時候增加堆內存的大小,并不能解決問題。通過前面學到的知識, 我們可以通過分配分析器找出大部分垃圾產生的位置。實際上在此示例中, 99%的對象屬于 Double
包裝類, 在readSensor
方法中創(chuàng)建。最簡單的優(yōu)化, 將創(chuàng)建的 Double
對象替換為原生類型 double
, 而針對 null 值的檢測, 可以使用 Double.NaN 來進行。由于原生類型不算是對象, 也就不會產生垃圾, 導致GC事件。優(yōu)化之后, 不在堆中分配新對象, 而是直接覆蓋一個屬性域即可。
對示例程序進行簡單的改造( 查看diff ) 后, GC暫停基本上完全消除。有時候 JVM 也很智能, 會使用 逃逸分析技術(escape ****ysis technique) 來避免過度分配。簡單來說,JIT編譯器可以通過分析得知, 方法創(chuàng)建的某些對象永遠都不會“逃出”此方法的作用域。這時候就不需要在堆上分配這些對象, 也就不會產生垃圾, 所以JIT編譯器的一種優(yōu)化手段就是: 消除內存分配。
以上就是GC調優(yōu)實戰(zhàn)之高分配速率High Allocation Rate的詳細內容,更多關于GC調優(yōu)高分配速率的資料請關注腳本之家其它相關文章!
原文鏈接:https://plumbr.io/handbook/gc-tuning-in-practice
相關文章
spring boot springMVC擴展配置實現(xiàn)解析
這篇文章主要介紹了spring boot springMVC擴展配置實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08Java虛擬機JVM之server模式與client模式的區(qū)別
這篇文章主要介紹了Java虛擬機JVM的client模式和Server模式兩者的區(qū)別和聯(lián)系2017-12-12Java spring webmvc如何實現(xiàn)控制反轉
這篇文章主要介紹了Java spring webmvc如何實現(xiàn)控制反轉,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-08-08Mybatis-plus中IService接口的基本使用步驟
Mybatis-plus是一個Mybatis的增強工具,它提供了很多便捷的方法來簡化開發(fā),IService是Mybatis-plus提供的通用service接口,封裝了常用的數(shù)據(jù)庫操作方法,包括增刪改查等,下面這篇文章主要給大家介紹了關于Mybatis-plus中IService接口的基本使用步驟,需要的朋友可以參考下2023-06-06JAVA簡單鏈接Oracle數(shù)據(jù)庫 注冊和登陸功能的實現(xiàn)代碼
這篇文章主要介紹了JAVA鏈接Oracle并實現(xiàn)注冊與登錄功能的代碼實例,有需要的朋友可以參考一下2014-01-01