JVM中的“白金之星”:超详细STW(Stop-The-World)解析
前言
给一些不知道白金之星的同学做一下介绍,白金之星是荒木飞吕彦笔下作品《JOJO的奇妙冒险》中一位主人公空条承太郎的替身,其能力之一是能暂停时间。在学习JVM的时候,看到了JVM中的STW机制,两者给我一种莫名的相似感,于是便有了以下这篇文章
正文
什么是 STW
?
STW
的全称是 "Stop The World"
,是指 JVM
在执行垃圾回收( GC )
时,会暂停所有正在执行的应用程序线程, 让垃圾回收器能够在一个稳定的环境中执行内存回收工作,以确保内存数据的一致性和完整性。
为什么需要STW
- 内存一致性:暂停所有应用线程,确保垃圾回收期间
内存状态一致
,防止内存修改导致数据错误。 - 简化实现:在
STW
期间,JVM
可以简化垃圾回收算法的实现,避免处理复杂的并发修改问题。 - 准确标记和清理:暂停线程使得
JVM
能够准确地标记和回收垃圾对象,避免遗漏或错误回收。
STW 的影响
- 暂停所有应用程序线程:在
STW
期间,应用程序的所有工作线程都会被暂停,只有GC
线程在运行。这意味着应用程序会停止处理任何用户请求和其他操作,直到垃圾收集完成。 - 影响应用程序性能:
STW
会导致应用程序暂停一段时间,这段时间可能会影响应用程序的响应时间和性能,特别是在对延迟敏感的应用程序中。
常见的垃圾回收算法与STW
Serial GC
- 特点:
单线程
垃圾回收。 - STW时间:相对
较长
,因为整个垃圾回收过程在一个线程中完成。 - 适用场景:适用于单核CPU或对暂停时间不敏感的应用。
Parallel GC
- 特点:
多线程
垃圾回收。 - STW时间:
比Serial GC短
,因为多个GC线程并行工作。 - 适用场景:适用于多核CPU,要求较高吞吐量的应用。
CMS(Concurrent Mark-Sweep) GC
- 特点:
并发标记
-清除算法。 - STW时间:标记阶段和清除阶段与应用线程并发执行,STW时间
较短
,但存在碎片化问题。 - 适用场景:适用于对暂停时间敏感的应用,如
Web
服务器。
G1(Garbage-First) GC
- 特点:面向
大堆内存
并发压缩算法。 - STW时间:设计目标是控制在可预测的短暂停时间内,分区管理内存,逐步回收。
- 适用场景:适用于大内存应用和需要可预测暂停时间的场景。
STW时间太短或太长会怎么样
STW时间太短的影响
-
频繁GC触发:频繁的
GC
触发会导致应用程序整体吞吐量下降,因为CPU
频繁用于GC
操作,而不是应用程序逻辑。 -
不完全回收:老年代中的垃圾可能不能被及时回收,导致内存利用率不高,可能最终触发
Full GC
。 -
应用抖动:这种抖动会影响应用程序的稳定性和响应时间,尤其是在对延迟敏感的应用中。
STW时间太长的影响
-
用户体验差:用户会感觉到明显的卡顿,甚至可能认为应用程序崩溃。尤其是实时交互系统(如游戏、金融交易系统等)影响更大
-
超时错误:客户端和其他系统可能会收到超时错误,影响业务流程和用户体验。
-
吞吐量下降:整体吞吐量下降,影响系统的性能和处理能力。
如何减少 STW 事件的影响
-
调优 JVM 参数:通过调整
JVM
的GC
参数,可以优化垃圾收集的性能,减少STW
时间。例如:-XX:MaxGCPauseMillis=<N>
:设置最大GC
暂停时间目标。-XX:GCTimeRatio=<N>
:设置GC
时间与应用程序运行时间的比例。-XX:+UseG1GC
:启用G1
垃圾收集器。-XX:+UseConcMarkSweepGC
:启用CMS
垃圾收集器。
-
监控和分析 GC 日志:定期监控和分析
GC
日志,可以了解STW
事件的频率和持续时间,识别出性能瓶颈并进行针对性的优化, 可以使用如JVisualVM
、GCViewer
等工具进行分析。 -
分代垃圾收集:
JVM
使用分代垃圾收集策略,将堆内存分为新生代和老年代,不同代使用不同的垃圾收集算法,新生代的回收速度更快,减少STW
时间。
实操感受
代码
输入以下命令启动
javac STWDemo.java
java -Xlog:gc,safepoint -Xms512m -Xmx512m STWDemo
结果:
[0.034s][info][gc] Using G1
开始
[0.169s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->0M(512M) 3.135ms
[0.170s][info][safepoint] Safepoint "G1CollectForAllocation", Time since last: 111977100 ns, Reaching safepoint: 108300 ns, At safepoint: 3609000 ns, Total: 3717300 ns
[0.188s][info][gc ] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 39M->0M(512M) 1.747ms
[0.188s][info][safepoint] Safepoint "G1CollectForAllocation", Time since last: 16346400 ns, Reaching safepoint: 75600 ns, At safepoint: 2145500 ns, Total: 2221100 ns
结束
逐行分析
-
GC(0) Pause Young (Normal) (G1 Evacuation Pause) 25M->0M(512M) 3.135ms
:- GC(0) :第一次垃圾回收事件。
- Pause Young (Normal) :年轻代的垃圾回收。
- G1 Evacuation Pause:
G1
垃圾回收器的Evacuation Pause
。 - 25M->0M(512M) :堆内存使用从
25MB
减少到0MB
,堆总大小为512MB。 - 3.135ms:暂停时间为3.135毫秒。
-
Safepoint "G1CollectForAllocation", Time since last: 111977100 ns, Reaching safepoint: 108300 ns, At safepoint: 3609000 ns, Total: 3717300 ns
:- Safepoint "G1CollectForAllocation" :触发
Safepoint
的原因:"G1CollectForAllocation"
。 - Time since last: 111977100 ns:距离上次
Safepoint
的时间:111977100纳秒(约111.9771毫秒)。 - Reaching safepoint: 108300 ns:进入
Safepoint
所花时间:108300纳秒(约108.3微秒)。 - At safepoint: 3609000 ns:在
Safepoint
停留时间:3609000纳秒(约3.609毫秒)。 - Total: 3717300 ns:总暂停时间(
STW
):3717300纳秒(约3.7173毫秒)。
- Safepoint "G1CollectForAllocation" :触发
-
GC(1) Pause Young (Normal) (G1 Evacuation Pause) 39M->0M(512M) 1.747ms
:- GC(1) :第二次垃圾回收事件。
- Pause Young (Normal) :年轻代的垃圾回收。
- G1 Evacuation Pause:
G1
垃圾回收器的Evacuation Pause
。 - 39M->0M(512M) :堆内存使用从39MB减少到0MB,堆总大小为512MB。
- 1.747ms:暂停时间为1.747毫秒。
-
Safepoint "G1CollectForAllocation", Time since last: 16346400 ns, Reaching safepoint: 75600 ns, At safepoint: 2145500 ns, Total: 2221100 ns
:- Safepoint "G1CollectForAllocation" :触发
Safepoint
的原因:"G1CollectForAllocation"
。 - Time since last: 16346400 ns:距离上次
Safepoint
的时间:16346400纳秒(约16.3464毫秒)。 - Reaching safepoint: 75600 ns:进入
Safepoint
所花时间:75600纳秒(约75.6微秒)。 - At safepoint: 2145500 ns:在
Safepoint
停留时间:2145500纳秒(约2.1455毫秒)。 - Total: 2221100 ns:总暂停时间(
STW
):2221100纳秒(约2.2211毫秒)。
- Safepoint "G1CollectForAllocation" :触发
总结🍊
其实换个思路想想,把 GC 想象成 JVM 里一直有个承太郎隔三岔五在砸瓦鲁多🕛,是不是挺有意思的。
转载自:https://juejin.cn/post/7377357813980512294