目录
- 简介
- CPU飙高问题
- 1、使用JVM命令排查CPU飙升100%问题
- 2、使用Arthas的方式定位CPU飙升问题
- 3、Java项目导致CPU飙升的原因有哪些?如何解决?
- OOM问题(内存溢出)
- 1、如何定位OOM问题?
- 2、OOM问题产生原因
- 死锁问题的定位
- 频繁Full GC问题
- 1、频繁FullGC会导致哪些问题
- 2、如何排查
- 3、导致频繁FullGC的原因
- 内存泄露问题的排查
- 磁盘空间不足,如何排查Linux中的大文件位置
- 尾声
简介
众所周知,咱们程序员在日常工作中肯定少不了会碰到系统故障、系统缓慢等问题,该笔记是我在工作中对一些OOM、CPU飙高、内存泄露等问题的排查笔记,现在分享出来,因为平时开发中我就是根据这些步骤进行排查的,没有附图,不喜勿喷。
CPU飙高问题
1、使用JVM命令排查CPU飙升100%问题
-
第一步:查看系统进程CPU使用率,首先使用【top】命令列出系统各个进程的资源占用,查看是哪个进程的CPU不对劲,看 %CPU 这个数值,然后找到对应的进程ID号。
-
第二步:查看当前进程号的线程CPU使用率,使用【top -Hp 进程ID】命令查看第一步查询出来的进程ID内的线程CPU使用情况。
-
第三步:找到CPU使用率高的线程并将PID转化为十六进制,将这个PID转化为十六进制【printf ‘0x%x’ PID】。
-
第四步:输出当前进程内的该线程的堆栈信息,有两种方式,
-
- 一种是使用jstack命令将之前的那个CPU高的进程输出到一个txt文件中【jstack 1677 > 1.txt】,然后使用vim或者grep根据十六进制去搜索。
-
- 另外一种就是直接使用【jstack 进程ID | grep 16进制的线程PID -A20】这个命令直接输出后20行,找到对应代码位置。
-
最后定位到具体业务代码的位置,我们去找到对应代码修改就行了。
2、使用Arthas的方式定位CPU飙升问题
-
第一步:需要去下载arthas的jar包:
wget https://arthas.aliyun.com/arthas-boot.jar
-
第二步:启动arthas:java -jar arthas-boot.jar
-
第三步:其实启动的时候就显示了CPU高的类了,但是我们可以通过命令查看:
查看系统整体运行情况:dashboard
查看最繁忙的线程【可以显示具体代码位置】:thread -n 1
查看繁忙的线程具体的代码:jad 【上面查询出来的包名+类名】
找到代码位置后,根据实际业务场景去做修改就行了。
3、Java项目导致CPU飙升的原因有哪些?如何解决?
CPU飙高的原因:
1、代码中存在大量的循环、递归或者重复计算等操作,导致程序运行时消耗大量的CPU资源。
2、代码中需要处理大量的数据,如果数据量过大或者处理不当,会导致CPU占用过高。
3、频繁的IO操作,代码中涉及到频繁访问数据库、频繁读写文件等操作,由于IO操作比较耗时,导致CPU占用过高。
4、代码中创建大量线程并处于长时间忙碌状态,会导致CPU占用过高。
5、多线程之间互相竞争同一资源,导致CPU不断切换线程从而消耗大量资源,会导致CPU占用过高。
6、JVM问题:配置不当或者性能问题导致CPU过高。
7、内存泄露也有可能导致CPU飙高。内存泄露的程序,JVM频繁进行FullGC尝试释放内存空间,进而会导致CPU飙升,内存泄露过多,造成可回收内存不足,程序申请内存失败,结果就是内存溢出。
如何解决:
- 优化代码逻辑,尽量减少重复计算和不必要的循环
- 合理处理大量数据,可以使用分页或者分批处理的方式。
- 检查并发处理的线程数量,合理控制线程的创建和销毁。
- 对JVM进行适当的配置和调优,如调整堆大小、垃圾回收策略等。
还是要根据具体的业务场景来进行优化。
OOM问题(内存溢出)
1、如何定位OOM问题?
一般出现OOM问题,java.lang.OutOfMemoryError:Java heap space
或者其他,我们都是通过dump文件去定位问题。
方式一(生产环境经常使用的方式):
我们工作里面都是在启动脚本里面提前加入:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/dump/
这个配置信息来提前预防,当出现OOM问题时,会将当前内存输出一个dump文件放入到指定的目录下(我这里是放到/logs/dump/目录下),然后将文件下载下来以后,通过MAT或者JProfile或者visualvm工具加载dump文件进行调试,查看最多跟业务有关的对象,然后找到GCRoot,查看线程栈的输出定位到具体代码位置。
方式二:使用命令主动导出
需要导出dump文件:【jmap -dump:format=b,file=1.hprof PID】或者通过Arthas工具;
2、OOM问题产生原因
-
一次性申请了太多的对象:比如说做一些列表的查询,一次性把所有的数据都给弄出来了,未做分页,假如数据达到了千万级,都查出来放入到List集合当中,有可能造成内存溢出。解决:更改申请对象的数量,比如说分页
-
内存资源耗尽未释放:比如说使用线程或者使用数据库查询,在高并发情况下不断创建线程,或者不断的创建数据库连接,但是又没有去释放,久而久之就会溢出,比如说流创建比较多,但是没有去关闭。使用池化技术去优化代码
-
本身资源不够:就是给堆分配的内存不够,因为在业务中可能会存在要创建大对象支撑业务正常运行,可以通过【jmap -heap PID :查看堆信息】。
-
内存泄露导致内存不够,最终OOM。
死锁问题的定位
通过arthas方式定位:
通过arthas的thread命令,使用【thread -b】命令输出线程的统计信息,BLOCKED表示目前阻塞的线程数,命令执行后会直接输出造成死锁的线程ID和具体的代码位置以及当前线程一共阻塞的线程数量。
通过命令方式定位:
1、使用【jps -l】命令列出所有进程;
2、使用【jstack 进程id | grep ‘deadlock’ -A 100】命令,通过jstack命令和死锁关键字打印出这个进程出现死锁的堆栈信息。里面的信息会打印出代码的具体位置,是哪些线程造成死锁。
3、根据打印出来的堆栈信息,找到代码具体位置,然后根据业务去解决。
频繁Full GC问题
1、频繁FullGC会导致哪些问题
-
系统卡顿:Full GC需要对整个堆进行扫描和处理,这个过程会导致应用程序的执行暂停。在这段时间内,应用程序无法响应用户请求,可能造成用户体验的下降或业务中断。
-
长时间延迟:Full GC的执行时间较长,特别是当堆内存较大时。如果Full GC发生的频率较高或执行时间过长,可能会导致系统的响应时间变慢,影响系统的性能和吞吐量。
-
内存占用过高从而影响其他服务正常运行:Full GC的执行通常需要消耗大量的CPU和内存资源。如果Full GC发生频繁且占用大量资源,可能会导致系统的资源消耗过高,从而影响其他应用程序或服务的正常运行。
-
系统不稳定:Full GC执行期间,应用程序可能处于一个不可预测的状态,因为可能会触发一些不符合预期的行为。这可能导致应用程序的异常行为、内存泄漏等问题。
2、如何排查
详细的我写在内存泄露那块的排查了。
1、首先找到对应的服务,使用jps找到线程PID;
2、使用【jstat -gcutil PID 1000 10
】查看GC情况,FGC指的是Full GC数量
timestamp S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
timestamp:是距离JVM启动的时间
S0、S1、E:分别是新生代的两个Survivor和Eden
O:是老年代区域
M:是Metaspace,元空间
CCS:使用压缩比例
YUC、YGCT:分别是新生代GC的次数和时间
FGC、FGCT:分别是老年代GC的次数和时间
GCT:GC的总时间
3、使用jstat -gccause查看额外输出上次GC的原因
4、使用jmap -dump:format=b,file=heapdump 线程ID;导出dump文件
5、使用MAT或者JProfile工具进行分析
3、导致频繁FullGC的原因
-
大对象,系统一次性加载了过多数据到内存:导致大对象直接进入到老年代;
-
内存泄露,频繁创建了大量对象,但是无法被回收,先引发Full GC,最后导致OOM。
-
程序频繁生成一些长声明周期的对象,当这些对象的平均年龄超过分代年龄时就会进入到老年代,最后老年代空间不够导致Full GC。
-
程序BUG。
-
代码里面显式调用了System.gc()。
-
JVM参数设置问题,包括总内存大小、新生代、老年代的大小、Eden区和幸存者S区的大小、元空间大小和垃圾回收算法等。
内存泄露问题的排查
不解决内存泄露会造成的影响:
-
现象一:进程可用内存会变少导致结果就是OOM,发生了这个问题后可能就会想到堆内存不够大,于是就将-Xmx参数调大,然后重启应用,但是过了一段时间后,OOM还是出现了。这个时候堆内存参数无法再调大,就只能每隔一段时间重启一下应用。
-
现象二:请求响应时间变长,这是因为频繁发生FullGC会暂停其他所有线程【Stop the world】造成的。
如何排查?
1、使用【jps -l
】命令定位到进程号,因为线上如果有应用出现OOM,那么这个应用的进程号用jps肯定能找到。
2、使用【jstat -gcutil -t -h8 进程ID 1000
】每隔1000ms打印一次该进程内存内GC活动情况,每8行输出一个行头信息。
root@8d36124607a0:/# jstat -gcutil -t -h8 进程ID 1000
timestamp S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
29.1 32.81 0.00 23.48 85.92 92.84 89.60 14 0.419 0 0.000 0.419
30.1 32.81 0.00 78.12 85.92 92.84 89.60 14 0.419 0 0.000 0.419
31.1 0.00 0.00 22.70 91.74 92.80 89.60 15 0.443 1 0.233 0.676
timestamp:是距离JVM启动的时间
S0、S1、E:分别是新生代的两个Survivor和Eden
O:是老年代区域
M:是Metaspace,元空间
CCS:使用压缩比例
YUC、YGCT:分别是新生代GC的次数和时间
FGC、FGCT:分别是老年代GC的次数和时间
GCT:GC的总时间
3、观察以上GC参数,可以发现老年代内存O的占用率根本没有下降,说明有的对象无法被回收,也有可能这些对象是长期有用的。
4、使用【jmap -dump:live,format=b,file=/log/dempFile/heap.bin 进程ID
】dump出内存快照到指定目录下,然后将这个heap.bin文件下载下来,然后使用MAT工具或者JProfile工具导入dump文件进行排查,找到【Leak Suspects】选项,工具会自动帮我们列出所有可能发生内存泄露的对象以及对象的个数,之后找到代码的对应位置根据业务进行修改即可。
造成内存泄露的情况(列举几种):
-
资源未关闭或释放导致内存泄露:在创建了各种连接【比如:数据库链接、网络IO和IO连接】,使用后未调用close()进行显式关闭,GC不会自动回收这些连接,大量对象未进行关闭就会引起内存泄露。
-
使用了ThreadLocal未进行remove()引发的内存泄露。
-
在代码中使用了大量的静态集合类:比如将HashMap、LinkedList等容器声明为静态,使得它们的生命周期与程序一致,容器内的元素在程序结束前不能被释放从而造成内存泄露。长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
-
单例模式:单例对象在初始化后会以静态变量的方式在JVM的整个声明周期中存在,如果单例对象持有外部引用,那么这个外部对象将不能被GC回收导致内存泄露。
-
定义变量的作用于大于其适用范围:当设置一个全局变量在使用完未将其设置为null,可能会造成内存泄漏。
磁盘空间不足,如何排查Linux中的大文件位置
1、使用du命令去排查(常用):du命令可以查看磁盘空间的使用情况,自然也可以用来查看磁盘上占用空间较多的文件和文件夹。
# 查看当前目录下最大的前几个文件
du -ahx . | sort -rh | head -5
#查看根目录下所有文件的大小
du -sh /*
2、使用find命令排查:
# 如果要找前10个大文件
find $home -type f -printf '%s %p\n' | sort -nr | head -10
尾声
其实线上问题的排查,一般就是top、free、df三连,然后再根据现象去定位是什么问题。
- top命令:是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。进程状态显示和控制,每5s中自动刷新一次(动态显示)。
- df指令:查看磁盘的使用率。
- free指令:可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区。
JVM常用的几个命令:
jps:查看正在运行的Java进程id
jstat:查看JVM内存的统计信息
jstack:打印JVM线程快照
jmap:导出内存映像文件&内存使用情况
.....
以上就是我平时开发中遇到的一些比较棘手的问题排查经验,没有带图片,不喜勿喷,后续等Arthas用熟了以后,再写一篇排查经验分享(二)的文章。