不知道大家在工作中有没有遇到过 线上代码CPU突然飙高,然后就会持久不下的情况?
如果有呢,大家肯定有解决方案了,如果没有的话可以来看看本篇教程,面试的时候可以吹一吹哦~
先给大家列几项可能Cpu 飙高的几种可能:
磁盘占用满,应用无法卡住无法运行(情况及其常见!!!我和朋友们都遇到过!!)
这种情况要怎么解决呢? 在服务器中 输入 df -h 命令 查看磁盘空间是否是100%状态
如果是100%状态,请删除一些log日志文件 / 删除无用的备份文件即可
其中 第 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
开搞!项目启动!
然后是时候和服务器 say hi了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siC4KEj7-1614932133559)(/Users/wangshuai/Library/Application Support/typora-user-images/image-20210305145919144.png)]
然后我们进服务器使用top 来查看cpu占用情况
CPU占用起来了!
我们看到进程的PID是 2581,如何确定CPU飙升的线程呢?
我们可以使用 top -Hp 2581来确定
确定到 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看看是哪些代码吧
问题找到了!就是这个!还看啥,改吧~!问题解决!
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 突然飙升~~~
第4点可能是我们分配的-Xmx -Xms堆大小过小,导致不能满足我们项目的运行,然后导致了频繁的GC(因为full GC有会导致 stop the world),这些可以通过以下来解决
首先,我们先启动一个项目,以下是启动参数
java -jar pay.jar -Xmx16m -Xms16m
项目启动成功!
然后假设运行一段时间以后,我们通过第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
关注隔壁老王,带您每天进步多一点~
今天您学废了嘛?_