Java GC线程停顿时间差异原因及停顿时间定义咨询
关于GC线程停顿日志的疑问解答
首先,先明确你日志里两个关键时间的准确定义,这是理解差异的基础:
1. “停顿时间”的准确定义
Total time for which application threads were stopped: 这个指标的计时起点是JVM发起“让所有应用线程进入安全点暂停”的请求后,第一个应用线程开始暂停的时刻,终点是最后一个应用线程从安全点恢复运行的时刻。它包含三部分时间:- 等待所有应用线程陆续到达安全点的耗时(也就是日志里的
Stopping threads took) - JVM在安全点内执行核心操作的耗时(比如GC的标记、清理、压缩,或是类卸载、偏向锁撤销等其他安全点操作)
- 唤醒所有线程的耗时(通常可以忽略不计)
- 等待所有应用线程陆续到达安全点的耗时(也就是日志里的
Stopping threads took: 这是从JVM发起安全点请求,到最后一个应用线程完全暂停、所有线程都进入安全点的耗时——这段时间里,线程是分批暂停的,不是同时停下。
2. 两次停顿差异巨大的原因
你看到的第一次173ms的停顿,大概率是一次Full GC触发的安全点停顿:
当老年代内存不足、或者需要进行整堆回收时,JVM会触发Full GC,这个过程需要在安全点内完成全局标记、清理、甚至老年代内存压缩等重量级操作,这些操作本身就会消耗大量时间,再加上等待线程进入安全点的时间,总停顿时长自然就长。
而第二次不足1ms的停顿,更可能是一次轻量级的安全点操作,而非GC触发:
JVM除了GC外,还有很多场景会触发安全点停顿,比如:
- 偏向锁的批量撤销
- 类元数据的更新或卸载
- JVM内部的统计信息收集
这类操作只需要让线程短暂暂停,几乎没有后续的重量级操作,所以总停顿时间几乎等于Stopping threads took的耗时,自然非常短。
3. GC时是否部分线程先挂起,其他线程仍在运行?
没错!JVM触发安全点停顿的时候,所有应用线程并不是同时瞬间暂停的:
- 处于阻塞状态(比如
sleep()、wait()、I/O阻塞)的线程会立刻响应暂停请求,直接进入安全点; - 正在执行代码的线程会继续运行,直到到达下一个“安全点”(比如循环迭代的边界、方法返回点、异常抛出点等预设的代码位置)才会暂停。
所以在Stopping threads took的这段时间里,确实是部分线程先挂起,剩下的线程在逐步到达安全点后暂停,直到所有线程都进入安全点,JVM才会开始执行GC的核心操作。
内容的提问来源于stack exchange,提问作者Gang Xie
相关产品推荐
相关产品推荐

