冬眠的笔记
首页文章分类书单项目关于
冬眠
X

© 2026 冬眠的笔记 · 用文字记录思考,用思考改变生活

首页>文章>Java
JavaJVMGCFullGC

线程 FullGC 问题排查

线上 FullGC 问题的常见诱因、jstat/jmap/MAT 等排查工具的使用以及典型案例分析

冬眠
冬眠
专注于技术、阅读与思考
2025-11-19
发布日期
10 min read
阅读时长
浏览量
线程 FullGC 问题排查

频繁GC

https://heapdump.cn/article/1870333

https://gceasy.io/

JVM性能问题排查

典型问题

  • CPU使用过高 —— CPU问题
  • JVM线程阻塞数过高 —— CPU问题
  • GC次数过高 —— 内存问题
  • 单次GC占时过高 —— 内存问题
  • JVM内存使用率过高 —— 内存问题
  • 慢查询数量过高 —— 慢请求

CPU占用过高

参考:

  • https://www.jianshu.com/p/f2ec38ddecff
  • https://www.cnblogs.com/lemon-flm/p/11775893.html

日常程序中常见的耗CPU的操作:

  • 频繁GC,访问量高时,有可能造成频繁的GC、甚至FGC。当调用量大时,内存分配过快,就会造成GC线程不停的执行,导致CPU飙高
  • 序列化与反序列化,程序执行xml解析的时,调用量增大的情况下,导致了CPU被打满
  • 加密、解密
  • 正则表达式校验,曾经线上发生一次血案,正则校验将CPU打满。大概原因是:Java 正则表达式使用的引擎实现是 NFA 自动机,这种引擎在进行字符匹配会发生回溯(backtracking)
  • 线程上下文切换、当启动了很多线程,而这些线程都处于不断的阻塞状态(锁等待、IO等待等)和执行状态的变化过程中。当锁竞争激烈时,很容易出现这种情况
  • 某些线程在做无阻塞的运算,简单的例子while(true)中不停的做运算,没有任何阻塞。写程序时,如果需要做很久的计算,可以适当将程序sleep下

CPU占用过高一般是线程问题,可以找到CPU占用最高的线程,然后排查线程部分的代码是否有问题。

那么如何找到对应的线程呢?这里提供两种方式:jvm 指令和arthas。

JVM指令方式

三步走:

  • 找到CPU占用最高的进程
  • 找到CPU占用最高的线程
  • 找到该线程对应的代码

找到CPU占用最高的线程

如果是容器部署的,基本上一个容器一个Java进程,直接用 jps 命令找到Java进程的PID即可;

如果不是容器部署的,一个服务器上有多个Java进程,则需要使用top命令来找到CPU占用最高的进程,获取PID。

找到CPU占用最高的线程

通过 top -Hp pid 命令列出对应进程下的所有线程信息,获取到CPU占用最高的线程的PID

$ top -Hp 48200

这里拿到的线程pid是十进制的,需要转换成16进制。

$ print "%x\n" 8925

找到该线程对应的代码

使用 jstack pid | grep tid -A30 找到对用的线程信息

$ jstack 48200 | grep 0x22dd -A30

然后在线程信息中找到服务的代码,针对性的看一下就可以了。

线程状态

一个Thread对象可以有多个状态,在java.lang.Thread.State中,总共定义六种状态:

  • NEW:线程刚刚被创建,也就是已经new过了,但是还没有调用start()方法,jstack命令不会列出处于此状态的线程信息
  • RUNNABLE:就绪状态,线程是可运行的,但并不是在运行中
  • BLOCKED:线程处于阻塞状态,正在等待一个monitor lock。通常情况下,是因为本线程与其他线程公用了一个锁。其他在线程正在使用这个锁进入某个synchronized同步方法块或者方法,而本线程进入这个同步代码块也需要这个锁,最终导致本线程处于阻塞状态。
  • WAITING:等待状态
  • TIME_WAITING:线程等待指定的时间
  • TERMINATED:线程已终止

在 dump 文件中,线程状态和Thread.State中定义的枚举略有不同:

  • Deadlock,死锁,两个线程针对临界资源相互等待
  • Runnable,执行中
  • Waiting on condition,等待资源,或等待某个条件的发生
  • Waiting on monitor entry,等待获取监视器,线程在等待进入一个临界区
  • Object.wait() 或 TIMED_WAITING,对象等待中
  • Suspended,暂停
  • Blocked,阻塞
  • Parked,停止

其中,Deadlock、Waiting on condition、Waiting on monitor entry和Blocked状态的线程需要重点关注。

如果发现有大量的线程都在处在 Wait on condition,从线程 stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。可以结合其他网络分析工具定位问题。

如果堆栈信息明确是应用代码,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。

Blocked,线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。

如果线程处于Blocked状态,但是原因不清楚。可以使用jstack -m pid得到线程的mixed信息。

Arthas

使用arths就比较简单了,启动arthas后进入到指定的应用,然后使用 thread -n 找到CPU占用最高的几个线程即可。

$ thread -n20

线程数过高

可能的问题点

  • 瞬时流量过大导致
  • 定时任务导致
  • 特殊的线程池:方法中创建了线程池,但是使用后没有及时关闭。

思路

排查线程数过高的思路和CPU占用过高的方式类似,也是使用 jstack 命令。

可以根据线程名称看看是不是哪个线程池过多,线程池名称 pool-xx-thread-xx 。

DefaultThreadFactory() {
    @SuppressWarnings("removal")
    SecurityManager s = System.getSecurityManager();
    group = (s != null) ? s.getThreadGroup() :
    Thread.currentThread().getThreadGroup();
    namePrefix = "pool-" +
        poolNumber.getAndIncrement() +
        "-thread-";
}

如果不存在线程很多的线程池,则使用jstack转存dump信息,可以在 fastthread.io 进行在线分析。

GC次数过高

gc动作主要发生在年轻代(YGC)、老年代(FullGC)和元空间。

FullGC次数过多,主要考虑两点:

  • 代码中一次获取了大量的对象,导致内存溢出,此时可以通过eclipse的mat工具查看内存中有哪些对象比较多;
  • 内存占用不高,但是Full GC次数还是比较多,此时可能是显示的System.gc()调用导致GC次数过多,这可以通过添加-XX:+DisableExplicitGC来禁用JVM对显示GC的响应。

可减少对象的创建,增大新生代以及调整幸存区

单次GC时间过长

参考:

  • https://heapdump.cn/article/1661497

动作

使用 jmap 转存dump文件

$ jmap -dump:format=b,file=heap pid

特别注意,转存dump文件时,整个应用会停滞,所以需要先摘掉该节点的流量再执行指令。

jstat 打印GC的情况,可以看到单位时间内的年轻代和老年代的GC次数和消耗时间。

$ jstat -gcutil pid 1000 10

CPU问题

arthas thread指令

jvm jstack指令

内存问题

  • 查询数据库导致
  • 嵌套循环导致
  • 远程调用慢
  • 方法慢导致的局部变量内存膨胀
  • 关键点要及时dump

慢请求

  • trace跟踪 —— arthas

常用工具

JVM监控

arthas: dashboard thread trace watch

JDK:jstack、jmap、jstat

GC log

  • -XX:+PrintGCDateStamps:打印 GC 发生的时间戳。

  • -XX:+PrintTenuringDistribution:打印 GC 发生时的代龄信息。

  • -XX:+PrintGCApplicationStoppedTime:打印 GC 停顿时长

  • -XX:+PrintGCApplicationConcurrentTime:打印 GC 间隔的服务运行时长

  • -XX:+PrintGCDetails:打印 GC 详情,包括 GC 前/内存等。

  • -Xloggc:../gclogs/gc.log.date:指定 GC log 的路径

GC log可配合 https://gceasy.io/ 在线分析工具使用。

文章标签

JavaJVMGCFullGC问题排查
Java 虚拟机配置
上一篇

Java 虚拟机配置

2025-11-19

Java 线程池详解
下一篇

Java 线程池详解

2025-11-19

冬眠

冬眠

博主

专注于技术、阅读与思考。在这里记录学习、思考与生活。

116
文章
2
分类
关注我
系列:JVM 实战

第 1 篇,共 3 篇

已是第一篇

下一篇

Java 虚拟机配置

文章目录

目录

  • JVM性能问题排查
  • CPU占用过高
  • JVM指令方式
  • Arthas
  • 线程数过高
  • 可能的问题点
  • 思路
  • GC次数过高
  • 单次GC时间过长
  • 参考:
  • 动作
  • 常用工具
  • GC log

相关文章

查看更多
Java 虚拟机配置

Java 虚拟机配置

2025-11-19 · 5 min read

元空间 MetaSpace 详解

元空间 MetaSpace 详解

2025-11-19 · 9 min read

JWT 基础知识

JWT 基础知识

2025-11-19 · 7 min read