开云·综合体育(kaiyun)-开云kaiyun中国官方网站并将干系信息汇总冒昧输出到日记-开云·综合体育(kaiyun)

开云kaiyun中国官方网站并将干系信息汇总冒昧输出到日记-开云·综合体育(kaiyun)

发布日期:2025-09-16 09:16  点击次数:66

开云kaiyun中国官方网站并将干系信息汇总冒昧输出到日记-开云·综合体育(kaiyun)

绪言开云kaiyun中国官方网站

本文将通过一个真确的坐褥环境案例,谨慎展示奈何系统性地排查和惩办JVM垃圾网络问题。这个案例涵盖了从问题发现、分析会诊到最终惩办的齐全历程,关于谐和JVM调优实战具有重要的参考价值。

系统布景

咱们的做事是一个高并发的微做事应用,手艺栈如下:

应用框架:Spring Boot 方针网络:Micrometer 监控系统:Datadog

证实:Micrometer 算作应用监控的门面库,扶持多种监控系统,包括:AppOptics、Atlas、Datadog、Dynatrace、Elastic、Ganglia、Graphite、Humio、Influx、Instana、JMX、KairosDB、New Relic、Prometheus、SignalFx、Stackdriver、StatsD、Wavefront 等。

谨慎信息请参考:https://micrometer.io/docs

问题快意

张开剩余95%

问题样貌

在日常监控中,咱们发现一个做事节点出现了严重的GC性能问题:

最大GC暂停时刻:无为跳跃400ms 峰值暂停时刻:达到546ms(发生时刻:02月04日 09:20:00)

GC暂停时刻监控图

业务影响

这种GC暂停时刻严重影响了业务运行:

做事超时:做事调用超不时刻为1秒,GC暂停过长导致超时风险 性能条件:

最大GC暂停时刻 < 200ms

平均暂停时刻 < 100ms

业务冲击:对客户的往返计谋产生了严重影响,亟需惩办

问题排查历程

第一步:系统资源使用分析

CPU负载分析

最初检查CPU使用情况,监控数据如下:

CPU负载监控图

不雅察罢了:

系统负载:4.92

CPU使用率:约7%

重要教唆:这个监控图中骨子隐含了重要痕迹(CPU中枢数与GC线程数的不匹配),但那时并未察觉颠倒。

GC 内存使用情况

然后咱们排查了这段时刻的内存使用情况:

从图中不错看到,约莫 09:25 傍边 old_gen 使用量大幅下落,确乎是发生了 FullGC。

但 09:20 前后,老年代空间的使用量在逐渐高涨,并莫得下降,也等于说激励最大暂停时刻的这个点并莫得发生 FullGC。

固然,这些是过后复盘分析得出的论断。那时对监控所反应的信息并不是绝顶信任,怀疑等于触发了 FullGC 导致的永劫刻 GC 暂停。

为什么有怀疑呢,因为 Datadog 这个监控系统,默许 10s 上报一次数据。有可能在这 10s 内发生些什么事情然则被漏报了(固然,这是不行能的,要是上报失败会在日记系统中打印干系的造作)。

再分析上头这个图,不错看到老年代对应的内存池是 “ps_old_gen”,通过前边的学习,咱们知谈,ps 代表的是 ParallelGC 垃圾网络器。

第三步:JVM建立分析

启动参数检查

检查JVM启动参数建立:

-Xmx4g -Xms4g

建立分析:

JDK版块:JDK 8 GC采用:未指定,使用默许的ParallelGC 堆内存:最大和启动堆内存均为4GB

初步问题假定

怀疑窦:ParallelGC可能是问题根源

谋略方向:ParallelGC主要追求系统最大费解量 量度计谋:为了费解量优化,会点火单次GC的暂停时刻 计算论断:可能因此导致暂停时刻过长

第一次优化尝试:切换到G1GC

优化计谋采用

采用G1垃圾网络器的事理:

踏实性:在JDK 8的新版块中,G1依然十分踏实

性能发达:具有精良的蔓延限度才智

适用场景:更稳妥低蔓延条件的应用

建立历程

初度建立(失败)

# 参数建立造作,导致启动失败

-Xmx4g -Xms4g -XX:+UseG1GC -XX:MaxGCPauseMills=50ms

造作分析:

参数名拼写造作:MaxGCPauseMills → MaxGCPauseMillis 参数值体式造作:50ms → 50

修正后建立

-Xmx4g -Xms4g -XX:+UseG1GC -XX:MaxGCPauseMillis=50

初步后果考证

做事启动得胜,通过健康检测切换到新节点后,监控泄漏:

G1GC初期后果监控图

后果评估:

GC暂停时刻:基本限度在50ms以内 相宜预期:达到了初期的优化方向

然则,问题远未完了.....

“彩蛋”惊喜

过了一段时刻,咱们发现了个底下这个惊喜(也许是惊吓),如下图所示:

中奖了,运行一段时刻后,最大 GC 暂停时刻达到了 1300ms。

情况似乎更恶劣了。

不时不雅察,发现不是个别快意:

内心是懵的,以为可能是方针算错了,比如把 10s 内的暂停时刻全部加到了通盘。

注册 GC 事件监听

于是想了个主义,通过 JMX 注册 GC 事件监听,把干系的信息凯旋打印出来。

关键代码如下所示:

// 每个内存池都注册监听

for (GarbageCollectorMXBean mbean

: ManagementFactory.getGarbageCollectorMXBeans()) {

if (!(mbean instanceof NotificationEmitter)) {

continue; // 假如不扶持监听...

}

final NotificationEmitter emitter = (NotificationEmitter) mbean;

// 添加监听

final NotificationListener listener = getNewListener(mbean);

emitter.addNotificationListener(listener, null, null);

}

通过这种表情,咱们不错在程序中监听 GC 事件,并将干系信息汇总冒昧输出到日记。

再启动一次,运行一段时刻后,看到底下这样的日记信息:

{

"duration":1869,

"maxPauseMillis":1869,

"promotedBytes":"139MB",

"gcCause":"G1 Evacuation Pause",

"collectionTime":27281,

"gcAction":"end of minor GC",

"afterUsage":

{

"G1 Old Gen":"1745MB",

"Code Cache":"53MB",

"G1 Survivor Space":"254MB",

"Compressed Class Space":"9MB",

"Metaspace":"81MB",

"G1 Eden Space":"0"

},

"gcId":326,

"collectionCount":326,

"gcName":"G1 Young Generation",

"type":"jvm.gc.pause"

}

情况确乎有点不妙。

此次实锤了,不是 FullGC,而是年青代 GC,而况暂停时刻达到了 1869ms。 少量意思意思都不讲,我认为这种情况分别理,而况不雅察 CPU 使用量也不高。

找了一大堆费事,试图施展这个 1869ms 不是暂停时刻,而仅仅 GC 事件的完了时刻减去动手时刻。

打印 GC 日记

既然这些妙技不靠谱,那就唯有祭出咱们的终极妙技:打印 GC 日记。

修改启动参数如下:

-Xmx4g -Xms4g -XX:+UseG1GC -XX:MaxGCPauseMillis=50

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps

再行启动,但愿此次能排查出问题的原因。

运行一段时刻,又发现了超长的暂停时刻。

分析 GC 日记

因为不触及明锐数据,那么咱们把 GC 日记下载到土产货进行分析。

定位到此次暂停时刻超长的 GC 事件,关键的信息如下所示:

Java HotSpot(TM) 64-Bit Server VM (25.162-b12) for linux-amd64 JRE (1.8.0_162-b12),

built on Dec 19 2017 21:15:48 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8)

Memory: 4k page, physical 144145548k(58207948k free), swap 0k(0k free)

CommandLine flags:

-XX:InitialHeapSize=4294967296 -XX:MaxGCPauseMillis=50 -XX:MaxHeapSize=4294967296

-XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

-XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC

2020-02-24T18:02:31.853+0800: 2411.124: [GC pause (G1 Evacuation Pause) (young), 1.8683418 secs]

[Parallel Time: 1861.0 ms, GC Workers: 48]

[GC Worker Start (ms): Min: 2411124.3, Avg: 2411125.4, Max: 2411126.2, Diff: 1.9]

[Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 2.7, Diff: 2.7, Sum: 16.8]

[Update RS (ms): Min: 0.0, Avg: 3.6, Max: 6.8, Diff: 6.8, Sum: 172.9]

[Processed Buffers: Min: 0, Avg: 2.3, Max: 8, Diff: 8, Sum: 111]

[Scan RS (ms): Min: 0.0, Avg: 0.2, Max: 0.5, Diff: 0.5, Sum: 7.7]

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.3]

[Object Copy (ms): Min: 1851.6, Avg: 1854.6, Max: 1857.4, Diff: 5.8, Sum: 89020.4]

[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.6]

[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 48]

[GC Worker Other (ms): Min: 0.0, Avg: 0.3, Max: 0.7, Diff: 0.6, Sum: 14.7]

[GC Worker Total (ms): Min: 1858.0, Avg: 1859.0, Max: 1860.3, Diff: 2.3, Sum: 89233.3]

[GC Worker End (ms): Min: 2412984.1, Avg: 2412984.4, Max: 2412984.6, Diff: 0.5]

[Code Root Fixup: 0.0 ms]

[Code Root Purge: 0.0 ms]

[Clear CT: 1.5 ms]

[Other: 5.8 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 1.7 ms]

[Ref Enq: 0.0 ms]

[Redirty Cards: 1.1 ms]

[Humongous Register: 0.1 ms]

[Humongous Reclaim: 0.0 ms]

[Free CSet: 2.3 ms]

[Eden: 2024.0M(2024.0M)->0.0B(2048.0K)

Survivors: 2048.0K->254.0M

Heap: 3633.6M(4096.0M)->1999.3M(4096.0M)]

[Times: user=1.67 sys=14.00, real=1.87 secs]

前后的 GC 事件都很平日,也没发现 FullGC 冒昧并发标志周期,但找到了几个可疑的点。

physical 144145548k(58207948k free):JVM 启动时,物理内存 137GB,恬逸内存 55GB。 [Parallel Time: 1861.0 ms, GC Workers: 48]:垃圾网络器责任线程 48 个。

GC 日记中揭露了几个关键信息,

user=1.67:用户线程耗时 1.67s; sys=14.00:系统调用和系统恭候时刻 14s; real=1.87 secs:骨子暂停时刻 1.87s; GC 之前,年青代使用量 2GB,堆内存使用量 3.6GB,存活区 2MB,可推断出老年代使用量 1.6GB; GC 之后,年青代使用量为 0,堆内存使用量 2GB,存活区 254MB,那么老年代约莫 1.8GB,那么“内存莳植量为 200MB 傍边”。

这样分析之后,不错得出论断:

年青代移动暂停,复制了 400MB 傍边的对象,却花消了 1.8s,系统调用和系统恭候的时刻达到了 14s。 JVM 看到的物理内存 137GB。 推算出 JVM 看到的 CPU 内核数目 72个,因为 GC 责任线程 72* 5/8 ~= 48 个。

看到这样多的 GC 责任线程我就动手警惕了,毕竟堆内存才指定了 4GB。

按照一般的 CPU 和内存资源配比,常见的比例差未几是 4 核 4GB、4 核 8GB 这样的。

望望对应的 CPU 负载监控信息:

通过和运维同学的疏通,得到这个节点的建立被截止为 4 核 8GB。

这样一来,GC 暂停时刻过长的原因就定位到了:

K8S 的资源艰涩和 JVM 未互助好,导致 JVM 看见了 72 个 CPU 内核,默许的并行 GC 线程扶植为 72* 5/8 ~= 48 个,然则 K8S 截止了这个 Pod 只可使用 4 个 CPU 内核的计较量,甚至 GC 发生时,48 个线程在 4 个 CPU 中枢上发生资源竞争,导致多半的凹凸文切换。

处置措施为:

截止 GC 的并行线程数目

事实施展,打印 GC 日记确乎是一个很灵验的排查分析模式。

截止 GC 的并行线程数目

底下是新的启动参数建立:

-Xmx4g -Xms4g

-XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:ParallelGCThreads=4

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps

这里指定了 -XX:ParallelGCThreads=4,为什么这样配呢?咱们望望这个参数的证实。

-XX:ParallelGCThreads=n

扶植 STW 阶段的并行 worker 线程数目。 要是逻辑处理器小于等于 8 个,则默许值 n 等于逻辑处理器的数目。

要是逻辑处理器大于 8 个,则默许值 n 约莫等于处理器数目的 5/8。在大多数情况下都是个比拟合理的值。要是是高建立的 SPARC 系统,则默许值 n 约莫等于逻辑处理器数目的 5/16。

-XX:ConcGCThreads=n

扶植并发标志的 GC 线程数目。默许值约莫是 ParallelGCThreads 的四分之一。

一般来说不必指定并发标志的 GC 线程数目,只用指定并行的即可。

再行启动之后,望望 GC 暂停时刻方针:

红色箭头所结合的点等于重启的时刻点,不错发现,暂停时刻基本上都处于 50ms 规模内。

后续的监控发现,这个参数确乎惩办了问题。

案例追思与念念考

中枢教师

通过这个齐全的排查案例,咱们不错得到以下重要教师:莫得量化,就莫得纠正。JVM问题排查和性能调优必须基于具体的监控数据进行。

使用的排查妙技

本案例讹诈了以下关键手艺妙技:

方针监控:通过Micrometer + Datadog成立齐全的监控体系 JVM参数调优:合理建立启动内存和垃圾网络器 GC日记分析:深刻分析GC日记定位根蒂原因 JMX事件监听:通过编程表情取得谨慎的GC事件信息

GC性能维度评估

蔓延维度:

主要热心GC暂停时刻 方向:最大暂停时刻 < 200ms,平均暂停时刻 < 100ms

费解量维度:

业务线程CPU资源占用百分比 GC影响要素:暂停时刻 + 并发GC的CPU花消

系统容量维度:

硬件建立合感性 做事处理才智匹配度

关键手艺洞悉

容器化环境的迥殊挑战:

容器资源截止与JVM感知的不匹配 GC线程数建立需要与骨子可用CPU中枢数对王人 K8s资源艰涩机制需要与JVM参数互助建立

问题排查的系统性模式:

快意不雅察 → 通过监控发现颠倒 假定考证 → 基于教师提倡问题假定 深刻分析 → 通过日记等妙技考证假定 惩办决策 → 针对根因制定惩办措施 后果考证 → 握续监控考证惩办后果

只消确保各项性能方针满足业务需求,资源占用保握在合理规模内,就达到了JVM调优的预期方向。

作家丨渺茫Jam

着手丨公众号:JAVA日知录 (ID:javadaily)

dbaplus社群接待宽敞手艺东谈主员投稿开云kaiyun中国官方网站,投稿邮箱:editor@dbaplus.cn

发布于:广东省

相关资讯
热点资讯
  • 友情链接:

Powered by 开云·综合体育(kaiyun) @2013-2022 RSS地图 HTML地图