生产CPU 100% 问题排查解决方案

生产CPU 100% 问题排查解决方案

生产CPU 100% 问题排查解决方案_第1张图片
不知道大家在工作中有没有遇到过 线上代码CPU突然飙高,然后就会持久不下的情况?

如果有呢,大家肯定有解决方案了,如果没有的话可以来看看本篇教程,面试的时候可以吹一吹哦~

先给大家列几项可能Cpu 飙高的几种可能:

生产CPU 100% 问题排查解决方案_第2张图片

  • 磁盘占用满,应用无法卡住无法运行(情况及其常见!!!我和朋友们都遇到过!!)

    这种情况要怎么解决呢? 在服务器中 输入 df -h 命令 查看磁盘空间是否是100%状态

    如果是100%状态,请删除一些log日志文件 / 删除无用的备份文件即可

生产CPU 100% 问题排查解决方案_第3张图片

  • 程序中出现while死循环,没有设置中断条件
  • 递归出现死循环,没有触发中断条件
  • 程序设置的堆大小处在Full GC边缘,导致疯狂的Full GC 100%
  • 其他

死循环导致CPU飙高

其中 第 2、3点是我们程序员人为的错误,这个可以在日志中找到相关的触发100%线程:

首先,我们得创造一个造成CPU100%的机会,here we go !

我们光速新建一个springboot 项目,然后光速写一个接口,流程是:

用户 -> 接口 -> 后端代码异常,导致cpu飙满

/**
 * @author 隔壁老王
 * @date 2021-03-05 14:51:54
 * @description:
 */
@RestController
public class TestController {


    @GetMapping("hi")
    public String hi(){
        cpu100();
        return "隔壁老王";
    }


    public static void cpu100(){
        while(true){
            new Runnable() {
                @Override
                public void run() {
                    while(true){
                        for (int j = 999; j < 999999; j++) {
                            System.out.println(j * j * j * j * j);
                        }
                    }
                }
            }.run();
        }
    }
}

然后呢,我们maven打包(mvn clean package)发到服务器中,暂时用笨办法,不使用jkins / docker 部署,直接使用java -jar

开搞!项目启动!

生产CPU 100% 问题排查解决方案_第4张图片

然后是时候和服务器 say hi了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siC4KEj7-1614932133559)(/Users/wangshuai/Library/Application Support/typora-user-images/image-20210305145919144.png)]

然后我们进服务器使用top 来查看cpu占用情况

生产CPU 100% 问题排查解决方案_第5张图片

CPU占用起来了!

我们看到进程的PID是 2581,如何确定CPU飙升的线程呢?

我们可以使用 top -Hp 2581来确定

生产CPU 100% 问题排查解决方案_第6张图片

确定到 PID为2592的线程占用满了我们的CPU,

接下来呢,我们要把 2592这个数字转换为16进制,因为后续我们需要用的到这个16进制数字

在linux中我们使用 printf “%x \n” 2592 命令来解决,得到的是a20

然后我们使用 jstack 项目进程PID | grep ‘a20’ -A 30 来确定具体的进程

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jstack 16091 | grep 'a20' -A 30
"http-nio-80-exec-1" #17 daemon prio=5 os_prio=0 tid=0x00007fdb64d18000 nid=0xa20 runnable [0x00007fdb331d9000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileOutputStream.writeBytes(Native Method)
        at java.io.FileOutputStream.write(FileOutputStream.java:326)
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
        - locked <0x00000000ece648e0> (a java.io.BufferedOutputStream)
        at java.io.PrintStream.write(PrintStream.java:482)
        - locked <0x00000000ecdf5a10> (a java.io.PrintStream)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        - locked <0x00000000ece64920> (a java.io.OutputStreamWriter)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.newLine(PrintStream.java:546)
        - eliminated <0x00000000ecdf5a10> (a java.io.PrintStream)
        at java.io.PrintStream.println(PrintStream.java:737)
        - locked <0x00000000ecdf5a10> (a java.io.PrintStream)
        
        ------------------------------------------------------------------------
        at com.wills.pay.controller.TestController$1.run(TestController.java:29)
        at com.wills.pay.controller.TestController.cpu100(TestController.java:33)
        at com.wills.pay.controller.TestController.hi(TestController.java:17)
        ------------------------------------------------------------------------
        
        
        
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

上面输出的横杠的内容,这里就是我们想要的答案,确定到代码的行数,我们来回到IDEA看看是哪些代码吧

生产CPU 100% 问题排查解决方案_第7张图片

问题找到了!就是这个!还看啥,改吧~!问题解决!

大量线程上下文切换

Java 代码

Copypublic static void main(String[] args) {
    // 大量线程 2000 同时并发,且进行等待
    for (int i = 0; i < 2000; i++) {
        new Thread(() -> {
            try {
                work();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
    System.out.println(1111111111);

}

private static void work() throws InterruptedException {
    Thread.sleep(10000L);
    System.out.println("----> do work now");
}

分析
生产CPU 100% 问题排查解决方案_第8张图片

类似上述案例一样,运行程序后,会发现 CPU 突然飙升~~~

频繁的GC导致我们CPU 飙高

第4点可能是我们分配的-Xmx -Xms堆大小过小,导致不能满足我们项目的运行,然后导致了频繁的GC(因为full GC有会导致 stop the world),这些可以通过以下来解决

首先,我们先启动一个项目,以下是启动参数

java -jar pay.jar -Xmx16m -Xms16m

项目启动成功!

生产CPU 100% 问题排查解决方案_第9张图片

然后假设运行一段时间以后,我们通过第2 3步确认到了是GC线程在搞怪,导致了我们项目的100%,那怎么知道我们的内存是否徘徊在100%左右呢?

除了我们比较常用的工具 例如: JVisualVM / JConsole / Arthas 外,如果我们啥工具没有,只有一个JDK环境要怎么解决呢?(常用的工具会在后续文章中介绍给大家使用~ 欢迎持续关注哦~)

没事,这些jdk官方给我们了几个比较有用的命令

我们可以使用 jps -l (如果使用jps参数普通的jar文件也都是显示Jar极其不友好)查看我们目标进程的PID:

我们可以看到 pay.jar的PID是14292,然后,我们可以使用 jinfo -flags pid 查询虚拟机运行参数信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYqWIIcS-1614932133574)(/Users/wangshuai/Library/Application Support/typora-user-images/image-20210305143416089.png)]

通过这一步,我们可以确定是不是我们的相关参数给少了

如果我们想要输出项目的相关配置和使用情况怎么办? 没事, jmap 为您排忧解难~

​ 1)jmap -heap pid:输出堆内存设置和使用情况(JDK11使用jhsdb jmap --heap --pid pid)

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jmap -heap 14292
using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 482344960 (460.0MB)
   NewSize                  = 10485760 (10.0MB)
   MaxNewSize               = 160759808 (153.3125MB)
   OldSize                  = 20971520 (20.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 17104896 (16.3125MB)
   used     = 10600864 (10.109771728515625MB)
   free     = 6504032 (6.202728271484375MB)
   61.975612128831415% used
Eden Space:
   capacity = 15269888 (14.5625MB)
   used     = 8765856 (8.359771728515625MB)
   free     = 6504032 (6.202728271484375MB)
   57.406157792381975% used
From Space:
   capacity = 1835008 (1.75MB)
   used     = 1835008 (1.75MB)
   free     = 0 (0.0MB)
   100.0% used
To Space:
   capacity = 1835008 (1.75MB)
   used     = 0 (0.0MB)
   free     = 1835008 (1.75MB)
   0.0% used
tenured generation:
   capacity = 37769216 (36.01953125MB)
   used     = 22821664 (21.764434814453125MB)
   free     = 14947552 (14.255096435546875MB)
   60.42398126558942% used

21377 interned Strings occupying 1951976 bytes.

作为 程序开发 老司机 新司机们,这种级别的英文应该很容易读意思吧?我们可以借助这些信息,来定位jvm信息

2)jmap -histo pid:输出heap的直方图,包括类名,对象数量,对象占用大小

jmap -histo 14292

 num     #instances         #bytes  class name
----------------------------------------------
   1:        101049       12782536  [C
   2:         11687        3219000  [I
   3:          9128        2386184  [B
   4:         86993        2087832  java.lang.String
   5:         38519        1232608  java.util.concurrent.ConcurrentHashMap$Node
   6:         10030        1110080  java.lang.Class
   7:          9458         832304  java.lang.reflect.Method
   8:         11254         802032  [Ljava.lang.Object;
   9:         16127         645080  java.util.LinkedHashMap$Entry
  10:         17951         574432  java.util.HashMap$Node
  11:          8143         541920  [Ljava.util.HashMap$Node;
  12:           175         423696  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  13:         21313         341008  java.lang.Object
  14:          5957         285936  java.util.HashMap
  15:          4224         270336  java.net.URL

由于篇幅关系,这里只截取一部分,本命令输出了类名、对象数量、对象占用大小

3)jmap -histo:live pid:同上,只输出存活对象信息

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jmap -histo:live 14292

 num     #instances         #bytes  class name
----------------------------------------------
   1:         71525        7431936  [C
   2:         69291        1662984  java.lang.String
   3:         38465        1230880  java.util.concurrent.ConcurrentHashMap$Node
   4:         10030        1110080  java.lang.Class
   5:          6879        1045552  [B
   6:          5768         863040  [I
   7:          9458         832304  java.lang.reflect.Method
   8:         15944         637760  java.util.LinkedHashMap$Entry
   9:         17940         574080  java.util.HashMap$Node
  10:          9083         567536  [Ljava.lang.Object;
  11:          8134         541152  [Ljava.util.HashMap$Node;
  12:           175         423696  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  13:         21297         340752  java.lang.Object
  14:          5952         285696  java.util.HashMap
  15:         11620         262968  [Ljava.lang.Class;
  16:          4568         255808  java.util.LinkedHashMap
  17:          4040         193920  org.springframework.core.ResolvableType
  18:          1683         134640  java.lang.reflect.Constructor
  19:          4102         131264  java.util.Hashtable$Entry
  20:          5151         123624  java.util.jar.Attributes$Name

4)jmap -clstats pid:输出加载类信息

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jmap -clstats 14292
Attaching to process ID 14292, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness........................................................................................liveness analysis may be inaccurate ...
class_loader    classes bytes   parent_loader   alive?  type

<bootstrap>     2341    4140403   null          live    <internal>
0x00000000edcbf648      1       1472    0x00000000ece8a030      dead    sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000ed173c10      1       1471    0x00000000ece8a030      dead    sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000ed105418      1       880     0x00000000ece8a030      dead    sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000ed192e10      1       880     0x00000000ece8a030      dead    sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000ed6a09e0      1       1471    0x00000000ecdaf148      dead    sun/reflect/DelegatingClassLoader

5)jmap -help:jmap命令帮助信息

如果以上的参数还不满足您窥探jvm内部的机密信息,那么这条命令可以帮助我们找到适合我们的那一条~

以上的这几条命令输出了这么多东西,看的头大啊,咋办? 没事, jstat 命令横空出世,全称是: ‘Java Virtual Machine statistics monitoring tool’ 可以用于监视JVM各种堆和非堆内存大小和使用量

1)jstat -class pid:输出加载类的数量及所占空间信息。

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jstat -class 14292
Loaded  Bytes  Unloaded  Bytes     Time   
  9366 17041.2        0     0.0       9.23

2)jstat -gc pid:输出gc信息,包括gc次数和时间,内存使用状况(可带时间和显示条目参数)

[root@iZbp1fl30mu3sgqrhczoirZ ~]# jstat -gc 14292
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1792.0 1792.0  0.0    0.0   14912.0   452.2    36884.0    22198.9   50264.0 47772.0 6744.0 6245.5    112    0.535   4      0.322    0.857

以上的信息可以帮助我们了解我们项目的 young GC / Full GC

上面的参数解释:

S0C、S1C、S0U、S1U:Survivor 0``/1``区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时

通过以上所有的命令,我们可以深入剖析项目内部的东西,发现占用内存大的对象,可以帮助我们解决项目内存占用过大的问题

总结

关键命令 汇总

top -Hp [pid]

printf “%x \n” [tid]

jstack [pid]|grep [16 进制 tid] -A 10

关注隔壁老王,带您每天进步多一点~

今天您学废了嘛?_

你可能感兴趣的:(Java,JVM,java)