当前位置: 首页 > news >正文

Java线上服务CPU与内存异常排查:从监控到代码的完整实战指南

1. 问题引入:当你的Java服务突然“高烧不退”

做后端开发的朋友,尤其是负责线上服务稳定性的同学,肯定对下面这个场景不陌生:监控大盘突然告警,某个核心服务的CPU使用率飙到了90%以上,或者内存占用像坐了火箭一样直线上升,眼看就要触发OOM(OutOfMemoryError)。这时候,整个团队的气氛都会瞬间紧张起来,业务方在催,老板在问,而你盯着满屏的日志和曲线图,感觉无从下手。

这种“高烧”状态,我们称之为性能瓶颈或资源泄漏。它不像代码报错那样有明确的异常栈,更像是一个隐形的杀手,悄无声息地拖慢整个系统,最终导致服务不可用。定位这类问题,考验的不仅仅是编码能力,更是一套系统的“诊疗”思路和工具使用技巧。很多人一上来就胡乱重启,或者盲目加机器,这只能暂时掩盖症状,病根没找到,迟早还会复发。

今天,我就结合自己多年在电商、金融等高压场景下处理这类问题的经验,整理出一套从现象到根因的完整定位流程。这套方法不是死记硬背的命令集合,而是一个有逻辑、分层次的“破案”指南。无论你是刚接触线上问题的初级工程师,还是想梳理自己排查思路的资深开发者,相信都能从中获得直接的帮助。我们会从最外部的监控指标入手,像剥洋葱一样,层层深入到JVM内部、线程堆栈乃至具体的代码行,最终找到那个“罪魁祸首”。

2. 整体排查思路:建立系统化的诊断路径

面对CPU或内存过高的问题,最忌讳的就是毫无章法地东一榔头西一棒子。一个高效的排查过程,应该像医生问诊一样,有望、闻、问、切的步骤。我总结了一个四层递进的排查模型,可以帮助你快速建立诊断路径。

2.1 第一层:现象确认与初步归因

告警响了,第一步不是马上登录服务器,而是先花几分钟看清“战场”全貌。你需要确认几个关键问题:

  1. 是全局性问题还是局部问题?查看监控,是所有实例的CPU/内存都高了,还是仅仅其中某一台或某几台?如果是局部的,很可能与那台机器上的特定请求或数据有关;如果是全局的,则要考虑最近是否有统一的变更,比如发布新代码、更新配置、或流量突增。
  2. 是瞬时尖峰还是持续高位?观察监控曲线。如果是瞬间飙升然后回落,可能是正常的流量脉冲或某个耗时任务;如果是持续缓慢上升,直到打满,那极有可能是内存泄漏;如果是持续高位震荡,则可能是代码中存在低效循环或资源竞争。
  3. 关联指标有何异常?CPU高时,观察接口的QPS(每秒查询率)和RT(响应时间)。如果QPS没变而RT增加,可能是单个请求处理变慢;如果QPS和CPU同步飙升,可能是流量过大。内存高时,观察GC(垃圾回收)频率和耗时。如果Full GC频繁但回收效果甚微,基本可以断定是内存泄漏。

这个阶段,利用好公司的监控系统(如Prometheus + Grafana, Zabbix等)是关键。初步判断能帮你决定排查的紧急程度和大致方向。

2.2 第二层:定位问题进程与线程

确认了现象,下一步就是登录到目标服务器,找到具体的“病灶”。这里我们主要依赖操作系统层面的工具。

对于CPU过高:首先用top命令查看整体资源情况。按1可以显示每个CPU核心的利用率,看是否是某个核心被打满。记住异常Java进程的PID(进程ID)。 然后,使用top -Hp [PID]命令,查看该Java进程内所有线程的CPU占用情况。你会看到一串线程ID(TID)和对应的CPU百分比。那个占用最高的线程,就是我们要重点关注的“热点线程”。记下它的TID(十进制数字)。

对于内存过高:同样先用top命令,但关注RES(常驻内存集)和%MEM(内存使用百分比)列。找到内存消耗最大的Java进程PID。 注意,Linux的top看到的内存是进程占用的物理内存,而JVM的内存管理更为复杂,我们还需要结合JVM工具看内部各区域的使用情况,这会在下一层进行。

注意top中的内存信息有时会因共享内存等原因导致统计偏差。对于Java进程,更准确的做法是使用jstatjcmd来观察JVM堆内存。

2.3 第三层:深入JVM内部洞察

这是定位Java应用问题的核心层。我们需要使用JDK自带的神兵利器。

工具一:jstack - 抓取线程快照jstack [PID] > thread_dump.log这个命令可以将指定时刻Java进程内所有线程的运行状态、调用栈信息输出到文件。拿到第二层找到的高CPU线程的TID(比如12345),我们需要将其转换为十六进制(0x3039),然后在thread_dump.log文件中搜索这个十六进制值,就能定位到该线程正在执行什么代码。如果它长时间停留在某个方法(比如一个死循环,或者一个锁等待),调用栈会清晰地告诉你。

工具二:jmap & jstat - 洞察内存奥秘

  • jmap -heap [PID]:查看堆内存的总体配置和使用情况,包括各代(Eden, Survivor, Old)的容量、使用量、GC算法等。快速判断是否是堆空间设置不合理。
  • jmap -histo:live [PID] | head -20:查看堆内存中存活对象的数量和大小统计,按总大小排序。排在前列的类,很可能就是内存消耗的“大户”或泄漏的嫌疑犯。注意:-histo:live会触发一次Full GC,线上慎用!非紧急情况可以用-histo(不触发GC)先看个大概。
  • jstat -gcutil [PID] 1000 10:每1秒(1000毫秒)输出一次GC统计信息,共10次。动态观察各内存区域的使用率(E, S0, S1, O, M分别代表Eden, Survivor0, Survivor1, Old, Metaspace)、GC次数(YGC, FGC)和耗时(YGCT, FGCT)。如果看到Old区(O)使用率不断上升,而FGC(Full GC)频繁但每次回收后O区使用率下降很少,这就是典型的内存泄漏迹象。

工具三:jcmd - 多功能瑞士军刀jcmd是JDK 7之后推荐的综合工具,功能强大且对进程影响相对较小。

  • jcmd [PID] Thread.print:等同于jstack,获取线程转储。
  • jcmd [PID] GC.heap_info:查看堆信息。
  • jcmd [PID] VM.native_memory:查看JVM申请的本地内存(堆外内存)详情,对于排查堆外内存泄漏(如Netty的Direct Buffer)至关重要。

2.4 第四层:代码级根因分析与验证

通过前三层,我们通常能锁定到可疑的线程栈和对象类。最后一层就是结合代码和业务逻辑进行验证。

  1. 分析线程栈:查看jstack输出的热点线程栈。如果栈顶是java.lang.Thread.run,可能是个死循环的线程;如果栈顶是sun.misc.Unsafe.parkjava.util.concurrent.locks.LockSupport.park,说明线程在等待锁,可能是锁竞争激烈导致CPU空转(虽然等待不消耗CPU,但争抢锁的过程会);如果栈顶是应用代码,比如com.xxx.service.Impl.process(),并且这个方法在栈里反复出现,那很可能就是这个方法逻辑有性能问题。
  2. 分析内存直方图:查看jmap -histo输出中排名靠前的类。如果是业务相关的类(如OrderDTO,UserSession),且数量多得离谱,就要去代码里找这些对象是在哪里创建、为什么没有被回收。结合jstack,看是否有线程持有这些对象的巨大集合(如HashMap、ArrayList)的引用,导致GC无法回收。
  3. 代码审查与验证:根据线索去翻代码。常见的内存泄漏场景包括:静态集合类持续添加元素、未关闭的资源(如数据库连接、文件流、HTTP连接)、监听器或回调注册后未取消、使用ThreadLocal未及时清理等。常见的CPU热点场景包括:低效的算法(如多层嵌套循环)、正则表达式误用、频繁的日志输出(尤其是DEBUG级别)、锁粒度不合理等。
  4. 复现与修复:如果可能,在预发或测试环境尝试复现问题。可以尝试使用ArthasAsync-Profiler等更强大的在线诊断工具进行动态跟踪和采样分析,精准定位热点方法。修复后,必须通过压测验证效果。

这套四层排查路径,从宏观到微观,从系统到代码,形成了一个闭环。在实际操作中,你可能需要在不同层次间来回跳跃验证,但有了这个框架,你的排查就不会迷失方向。

3. CPU使用率过高问题深度定位

CPU使用率居高不下,意味着处理器一直在忙碌。对于Java应用,这通常不是“计算过于复杂”,而是“在不该忙碌的地方瞎忙”。下面我们拆解几种典型场景和对应的排查手术刀。

3.1 场景一:无限循环或低效算法

这是最直接的原因。某个线程陷入了一个无法退出的循环,或者一个算法的时间复杂度太高。

排查手法:

  1. 使用top -Hp找到耗CPU的线程TID
  2. jstack获取线程转储,并转换TID为十六进制进行搜索
  3. 分析线程栈。如果你在栈顶看到类似下面的调用,并且栈深度很浅,反复循环,那很可能就是问题所在:
    "Thread-0" #1 prio=5 os_prio=0 tid=0x00007f4874000 nid=0x3039 runnable [0x00007f48fe000] java.lang.Thread.State: RUNNABLE at com.example.BadService.infiniteLoop(BadService.java:20) // 卡在某个方法 at com.example.BadService.lambda$start$0(BadService.java:15) at com.example.BadService$$Lambda$1/0x0000000840060840.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)
  4. 使用Profiler工具进行热点方法采样jstack是瞬态图,如果问题不是持续占满CPU,可能抓不到。这时可以用Arthasprofiler命令,或者Async-Profiler,对CPU进行一段时间(比如30秒)的采样,它会生成一个火焰图。火焰图横向显示调用栈,纵向显示深度,最宽的那个“火苗”就是最耗CPU的方法。一眼就能看出系统的“热力”分布。

实操心得:

  • 不要忽视“日志轰炸”。在循环里打log.debug(...),如果日志框架配置不当(例如异步队列满),或者日志级别实际是INFO/ERROR但日志内容字符串拼接耗时,也可能导致CPU升高。检查日志配置和日志语句。
  • 正则表达式,尤其是复杂的、在循环中编译的Pattern.compile(),是CPU杀手。确保Pattern对象被缓存复用。

3.2 场景二:激烈的锁竞争

这是高并发场景下的典型问题。线程没有在执行业务计算,而是在为了获取锁而“自旋”空转,或者大量线程处于BLOCKED状态。

排查手法:

  1. 分析jstack文件中的锁信息。搜索BLOCKED状态的线程。查看它们等待的锁是什么(waiting to lock <0x000000076bf62200>),以及是哪个线程持有了这个锁(locked <0x000000076bf62200>)。
    "Thread-2" #3 prio=5 os_prio=0 tid=0x00007f4874000 nid=0x303b waiting for monitor entry [0x00007f48fd000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.ResourceService.consume(ResourceService.java:50) - waiting to lock <0x000000076bf62200> (a java.lang.Object) // 等待这个锁 at com.example.Worker.run(Worker.java:25) "Thread-1" #2 prio=5 os_prio=0 tid=0x00007f4874000 nid=0x303a runnable [0x00007f48fe1000] java.lang.Thread.State: RUNNABLE at com.example.ResourceService.consume(ResourceService.java:55) - locked <0x000000076bf62200> (a java.lang.Object) // 正持有这个锁 at com.example.Worker.run(Worker.java:25)
  2. 统计锁竞争热度。如果大量线程在等待同一个锁,说明这个锁的粒度太粗,成了性能瓶颈。可以使用Arthasmonitor命令监控某个方法的调用耗时和成功率,或者使用thread -b命令直接找出当前阻塞其他线程最多的那个“罪魁祸首”线程。

解决方案:

  • 缩小锁粒度:将一个大锁拆分成多个小锁,例如不用synchronized锁整个方法,而是锁一个更小的对象。
  • 使用并发容器:用ConcurrentHashMap代替Collections.synchronizedMap(new HashMap())
  • 使用读写锁:对于读多写少的场景,使用ReentrantReadWriteLock
  • 尝试无锁编程:考虑使用Atomic变量或LongAdder
  • 检查死锁jstack输出最后一部分通常会做死锁检测,如果发现会明确提示Found one Java-level deadlock:

3.3 场景三:频繁的GC活动

这是一个容易被忽略的间接原因。当应用程序产生大量短命对象,或者存在内存压力时,垃圾回收器(尤其是Young GC)会频繁启动。虽然单次GC停顿时间不长,但极高的频率会累积消耗大量CPU时间。

排查手法:

  1. 使用jstat -gcutil [PID] 1000动态观察。重点看YGC(Young GC次数)和YGCT(Young GC总时间)这两列。如果YGC在短时间内(比如1分钟)疯狂上涨几十、上百次,并且YGCT累积时间很长,说明GC是CPU消耗大户。
  2. 结合top观察。此时top看到的CPU高,可能不是业务线程,而是JVM的GC线程(如G1 Main Marker,GC Thread等)。

解决方案:

  • 优化对象创建:减少不必要的对象创建,特别是在循环和高频调用路径上。重用对象(使用对象池需谨慎,避免引入复杂性)。
  • 调整堆和代大小:如果Survivor区太小,会导致对象过早进入老年代,可能引发更耗时的Mixed GC或Full GC。适当调大年轻代(-Xmn)可能有助于减少晋升。
  • 选择合适的GC器:对于高吞吐量应用,Parallel GC可能更合适;对于低延迟要求,G1或ZGC是更好的选择。但这需要根据应用特性和性能测试来决定。

4. 内存使用率过高问题深度定位

内存问题比CPU问题更具潜伏性,往往在积累到一定程度后才突然爆发。其核心矛盾是:创建的对象速度 > 垃圾回收的速度

4.1 场景一:堆内内存泄漏

这是最常见的Java内存问题。对象因为被错误的引用持有而无法被GC回收,随着时间推移,这些“垃圾”对象占满整个堆,最终触发OOM。

排查手法(标准流程):

  1. 监控观察:通过jstat -gcutil观察老年代(O)使用率是否随时间持续上升,且Full GC(FGC)后回收效果很差(使用率下降不明显)。
  2. 生成堆转储文件:这是内存分析最关键的证据。在内存占用较高时,使用命令生成堆转储(Heap Dump)文件。
    • jmap -dump:live,format=b,file=heap.hprof [PID]:触发一次Full GC后转储存活对象,文件较小,但会干扰线上服务。
    • 推荐(JDK自带)jcmd [PID] GC.heap_dump /path/to/heap.hprof:影响相对较小。
    • 推荐(安全):在JVM启动参数中添加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump,让JVM在发生OOM时自动生成转储,最安全且能捕获到“案发现场”。
  3. 使用MAT或JVisualVM分析堆转储
    • MAT (Eclipse Memory Analyzer):功能极其强大。打开heap.hprof文件后:
      • 首先看Leak Suspects Report(泄漏嫌疑报告),它会自动分析可能的内存泄漏点。
      • 使用Histogram(直方图)视图,按类或类加载器统计对象数量和占用内存。与jmap -histo类似,但更直观。
      • 最关键的一步:找到疑似泄漏的大对象(比如一个巨大的HashMap),右键选择Path To GC Roots -> exclude weak/soft references。这个功能会显示从GC根节点(如静态变量、活动线程栈)到这个对象的完整引用链。这条链就是阻止该对象被回收的“罪证”!通常你会发现,某个静态的MapList在持续添加业务对象,却从未清理。
    • JVisualVM:JDK自带,比较轻量。加载堆转储后,查看“类”视图,同样可以按大小排序,并查看实例的引用关系。

常见泄漏点:

  • 静态集合类:如public static Map CACHE = new HashMap();是最经典的泄漏源。
  • 连接池、线程池未正确配置:连接或线程创建后不释放。
  • 监听器和回调:注册后没有注销。
  • ThreadLocal使用不当:在线程池场景下,线程复用导致前一个任务的ThreadLocal变量未被清除,从而随着线程存活一直累积。
  • 内部类持有外部类引用:非静态内部类会隐式持有外部类实例的引用,如果这个内部类对象被长生命周期对象引用,会导致外部类实例也无法释放。

4.2 场景二:堆外内存泄漏

JVM的内存不止堆(Heap),还有堆外内存(Native Memory),也叫直接内存。像NIO的DirectByteBuffer、JNI调用、某些网络框架(如Netty)的池化内存管理,都会使用堆外内存。这部分内存不受JVM垃圾回收管理,需要手动释放或依赖框架的生命周期管理。

排查手法:

  1. 现象识别top看到进程的RES内存持续增长,但通过jstatjmap看到的堆内存使用率却很正常,甚至很低。这就是堆外内存泄漏的典型特征。
  2. 使用jcmd查看详情jcmd [PID] VM.native_memory命令可以详细展示JVM内部各部分内存的使用情况,包括Internal(元数据)、Arena(分配器)、Thread(线程栈)、Code(JIT代码缓存)以及Direct(直接缓冲区)。关注Direct部分的committed内存是否异常高且持续增长。
  3. 使用操作系统工具pmap -x [PID] | sort -k3 -n -r可以查看进程的内存映射,找到那些巨大的匿名映射块(anon),它们可能对应着堆外内存区域。
  4. 定位代码:堆外内存泄漏定位更难。如果使用了Netty,可以开启Netty的泄漏检测工具(ResourceLeakDetector)。对于DirectByteBuffer,可以尝试在代码中跟踪其创建和释放,或者使用Java Flight Recorder (JFR)来监控DirectByteBuffer的分配事件。

解决方案:

  • 确保DirectByteBuffer在使用后调用cleaner.clean()(通常通过((DirectBuffer) buffer).cleaner().clean())或等待GC触发其Cleaner机制。但在高并发下,最好池化复用。
  • 检查使用的第三方库(尤其是网络库、序列化库)是否有已知的堆外内存泄漏问题,并升级到修复版本。

4.3 场景三:元空间(Metaspace)溢出

Java 8之后,永久代(PermGen)被元空间(Metaspace)取代。它主要存储类的元数据(Klass结构、方法信息等)。如果应用动态生成大量类(例如大量使用CGLib、ASM进行字节码增强,或者Groovy等脚本引擎频繁编译),就可能导致元空间溢出,错误为Metaspace

排查手法:

  1. 监控观察jstat -gcutil中的M(Metaspace)使用率是否接近100%。
  2. 分析原因:元空间溢出几乎总是和动态类加载有关。检查应用是否使用了大量的反射、动态代理、或者像Spring AOP(CGLib代理)在反复创建代理类。

解决方案:

  • 适当调大元空间大小:-XX:MaxMetaspaceSize=256m
  • 更根本的是,优化应用设计,减少不必要的动态类生成。例如,检查是否有循环里在反复创建代理对象。

5. 高级工具与实战技巧

除了JDK自带的基础工具,掌握一些更强大的“武器”能让你在排查时事半功倍。

5.1 Arthas:阿里开源的在线诊断利器

Arthas不需要修改代码、不需要重启服务,就能动态跟踪问题,是线上排查的“核武器”。

常用命令场景:

  • dashboard:实时仪表盘,一眼看清线程、内存、GC、运行时信息。
  • thread [PID]thread -n 3:查看所有线程状态,或CPU占用率最高的前3个线程。比top -Hp+jstack方便太多。
  • thread -b:一键找出当前阻塞其他线程最多的线程(死锁或锁竞争瓶颈)。
  • jad com.example.BadService infiniteLoop:反编译线上正在运行的类字节码,直接看源码。当你怀疑代码版本不对时特别有用。
  • watch com.example.Service methodName '{params, returnObj, throwExp}':动态观察方法调用的入参、返回值和异常。用于追踪数据流向。
  • profiler start/profiler stop:生成CPU或内存分配的火焰图,图形化展示热点。
  • heapdump:在线生成堆转储文件,功能同jmap -dump

实操心得:Arthas的命令非常丰富,刚开始不必全部掌握。记住dashboardthreadjadwatchprofiler这几个最常用的,就能解决80%的问题。使用前最好在测试环境熟悉一下,避免对线上服务造成意外影响。

5.2 性能剖析与火焰图

火焰图是性能分析的“心电图”,它能将复杂的调用栈和耗时情况可视化。

  • CPU火焰图:看最顶层的“平顶山”,那里就是消耗CPU最多的调用链。
  • 内存分配火焰图:看哪些方法在频繁地分配对象。

生成方法:

  1. 使用Async-Profiler:下载工具,通过profiler.sh脚本附加到Java进程,可以生成非常清晰的SVG格式火焰图。
  2. 使用Arthas的profiler命令:集成在Arthas中,使用更方便,profiler start开始采样,profiler stop停止并生成火焰图文件。

看图技巧:

  • X轴不代表时间,而是采样数量,宽度越宽,表示被采样到的次数越多,即越耗时。
  • Y轴是调用栈深度,从上到下是调用关系。
  • 鼠标悬浮可以看具体方法的采样百分比。
  • 寻找最宽且位于中下层的“火苗”,那就是你要优化的热点方法。

5.3 GC日志分析与优化建议

GC日志是理解JVM内存行为的金钥匙。务必在启动参数中开启GC日志。

推荐参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M

对于JDK 9+,推荐使用更统一的日志框架:

-Xlog:gc*,gc+age=trace,safepoint:file=/path/to/gc.log:time,uptime,level,tags:filecount=5,filesize=10M

分析要点:

  1. Young GC频率和耗时:如果频率过高(如几秒一次),考虑增大年轻代(-Xmn)。如果单次耗时过长,检查是否有很多“大对象”直接进入了老年代(参数-XX:PretenureSizeThreshold)。
  2. Full GC频率和原因:关注每次Full GC的触发原因(如Allocation Failure,Metadata GC Threshold,System.gc())。如果是由Allocation Failure触发且频繁发生,说明老年代空间不足,可能需要增大堆(-Xmx)或优化程序减少内存占用。
  3. GC后内存回收效果:看每次GC后,各区域的使用量下降是否明显。如果老年代每次Full GC后使用率下降很少,就是内存泄漏的铁证。

优化思路:

  • 首要目标:避免或减少Full GC。
  • 核心策略:让对象在年轻代就“死掉”。调整-Xmn(年轻代大小)、-XX:SurvivorRatio(Eden和Survivor比例),让对象有足够的时间在Young GC中被回收。
  • 选择GC器:吞吐量优先用Parallel GC,低延迟优先用G1或ZGC。G1需要设置合理的-XX:MaxGCPauseMillis(期望最大停顿时间,如200ms),ZGC则几乎全程并发,停顿时间极短,但吞吐量可能略有损耗。

6. 常见问题排查清单与避坑指南

这里将高频问题整理成表,方便你快速对照排查。

现象可能原因排查工具/命令关键证据/下一步动作
CPU使用率持续100%1. 某个线程死循环
2. 频繁的Young GC
3. 锁竞争激烈(自旋)
1.top -Hp+jstack
2.jstat -gcutil
3.jstackBLOCKED线程
1. 找到热点线程栈,定位代码。
2. 观察YGC次数是否暴增。
3. 找到被大量线程等待的锁。
CPU使用率间歇性飙升1. 定时任务触发
2. 外部调用超时/重试风暴
3. 缓存失效导致的雪崩查询
1. 关联监控时间点与任务日志。
2. 检查依赖服务状态和超时配置。
3. 分析调用链,查看数据库/缓存慢查询。
1. 检查调度中心日志。
2. 使用Arthastrace命令追踪调用链耗时。
3. 查看DB监控和慢SQL日志。
堆内存缓慢增长直至OOM内存泄漏(对象无法回收)1.jstat -gcutil观察O区趋势。
2.jmap -histojcmd GC.heap_info
3. 生成堆转储用MAT分析。
1. O区使用率只升不降,FGC无效。
2. 发现某个业务类实例数异常多。
3. MAT中找到该对象的GC Roots引用链。
物理内存高但堆内存使用正常堆外内存泄漏(Direct Buffer等)1.top看RES,jstat看堆。
2.jcmd VM.native_memory
3.pmap -x查看进程内存映射。
1. RES高,堆内存使用率低。
2. Native Memory的Committed持续增长。
3. 存在巨大的匿名内存映射块。
服务卡顿,但CPU不高1. 外部依赖(DB、Redis、RPC)慢
2. 锁竞争导致线程大量WAITING
3. 频繁的Full GC导致“世界暂停”
1. 链路追踪(SkyWalking, Zipkin)。
2.jstack统计线程状态比例。
3. 分析GC日志,看Full GC频率和耗时。
1. 发现某个下游调用耗时极长。
2. 大量线程处于WAITING (parking)
3. GC日志中出现长停顿的Full GC记录。
Metaspace溢出动态类加载过多(CGLib代理等)1.jstat -gcutil看M区。
2. 检查是否大量使用反射、动态代理。
1. M区使用率100%。
2. 错误信息为Metaspace。增加-XX:MaxMetaspaceSize并优化代码。

避坑指南与心得:

  1. 预防大于治疗:在编码阶段就建立良好的习惯。避免在循环中创建大量临时对象、谨慎使用静态集合、及时关闭资源(用try-with-resources)、合理设计缓存失效策略。
  2. 监控与告警先行:务必搭建完善的应用性能监控(APM)体系。监控核心指标:CPU使用率、堆内存使用率、GC频率与耗时、接口QPS/RT、错误率。设置合理的告警阈值,能在问题萌芽阶段就发现。
  3. 保留现场:遇到线上问题,条件允许的话,先保留现场(如线程Dump、堆Dump),再重启。重启会丢失一切线索。可以设置JVM参数-XX:+HeapDumpOnOutOfMemoryError自动留证。
  4. 理解工具原理:不要死记命令。理解jstack是看线程瞬间的快照,jstat是看JVM内部的趋势,jmap是看内存的静态分布。根据不同场景选择合适的工具。
  5. 保持怀疑,交叉验证:工具给出的信息有时会有误导。比如top看到的CPU高,不一定是Java业务代码,可能是GC。需要用jstat和线程栈交叉验证。MAT分析出的“泄漏点”,也要回到代码逻辑中确认是否合理。
  6. 性能优化要有数据支撑:不要凭感觉优化。优化前用工具(Arthas profiler, JMH基准测试)定位真正的瓶颈,优化后用同样的工具和数据对比验证效果。
http://www.jsqmd.com/news/1387898/

相关文章:

  • 【研发类-架构设计Skills】cloud-architect 技能
  • 储水式电热水器选购指南:从能效、功率到安全技术的深度解析
  • 音乐怎么改成MP3格式?5种转换方法实测,看看你适合哪一种!
  • 多账号SSH配置与管理实战指南
  • 避免数据库回填:数据模式演进的设计思维与工程实践
  • 2026年杭州企业选择GEO优化公司必读:五家服务商横向评测与避坑指南 - 品牌报告
  • 三步搞定表情字体统一显示:EmojiOne Color 安装与 Web 引入完整指南
  • 手机版ChatGPT里复制代码怎么用?AI导出鸭鸿蒙版批量导出无损搞定
  • Java老码农转型AI:RAG检索准确率从60%到90%的三大优化技巧
  • AI智能体集群安全:无防御攻击面剖析与纵深防护实战
  • 忻州企业网站建设:从入门到精通,打造高转化率的本土化数字门户
  • 软件工程毕业设计容易的方向大全
  • 10_阿里云应用型负载均衡 ALB 配置指南:从公网入口到后端服务的完整链路
  • 如何从零开始建设一个地方门户网站?普通人也能做的本地生活创业指南
  • 水电站水下结构检测与排水管网巡检方案
  • Not All Prefills Are Equal:PPD Disaggregation for Multi-turn LLM Serving并非所有预填充都相同:面向多轮LLM服务的PPD分离架构
  • 蓝速科技丨OpenHarmony 信创落地:嵌入式终端国产化突围实战
  • DeepSeek-V4 Flash本地部署实战:高性能大模型私有化集成指南
  • 鞍山网站建设找金航:揭秘本地企业数字化转型的幕后真相与避坑指南
  • 重庆微网站建设哪家好且性价比高且响应速度快且安全稳定的靠谱公司推荐
  • 使用ftrace跟踪KVM内核行为
  • 户外作业对讲机选手持还是车载?黑龙江外勤场景设备搭配指南
  • HTTPSConnectionPool(host=\‘huggingface.co\‘, port=443 Retrying in 1s [Retry 1/5].
  • 2026年最新重庆市轨道交通图和 重庆轨道交通规划图 附图
  • 为什么你学网安越学越焦虑?破解新人普遍的内耗式学习心理
  • AI绘画按张付费:用Token API把单张成本打到几分钱
  • 企业级LLM微调实战:5万条财税SFT数据构建 + Qwen2.5-72B LoRA训练全记录
  • Navicat试用期到期别慌,一个命令行工具让你免费重置15天试用
  • 如何高效地建设一个企业网站以获取精准客户与品牌信赖
  • python数据可视化技巧的100个练习 -- 85. 使用 Plotly 创建饼图