电商网站文档,前端开发培训机构排名前十,八桂职教网技能大赛,wordpress 弹窗打开文章目录 前言cpu占用过高死锁内存泄漏上面只是其中一种处理方法 总结 前言
最近很多小伙伴跟我说#xff0c;自己学了不少JVM的调优知识#xff0c;但是在实际工作中却不知道何时对JVM进行调优。今天#xff0c;我就为大家介绍几种JVM调优的场景。
在阅读本文时#xff… 文章目录 前言cpu占用过高死锁内存泄漏上面只是其中一种处理方法 总结 前言
最近很多小伙伴跟我说自己学了不少JVM的调优知识但是在实际工作中却不知道何时对JVM进行调优。今天我就为大家介绍几种JVM调优的场景。
在阅读本文时假定大家已经了解了运行时的数据区域和常用的垃圾回收算法也了解了Hotspot支持的垃圾回收器。 cpu占用过高
cpu占用过高要分情况讨论是不是业务上在搞活动突然有大批的流量进来而且活动结束后cpu占用率就下降了如果是这种情况其实可以不用太关心因为请求越多需要处理的线程数越多这是正常的现象。话说回来如果你的服务器配置本身就差cpu也只有一个核心这种情况稍微多一点流量就真的能够把你的cpu资源耗尽这时应该考虑先把配置提升吧。
第二种情况cpu占用率长期过高这种情况下可能是你的程序有那种循环次数超级多的代码甚至是出现死循环了。排查步骤如下
1用top命令查看cpu占用情况 这样就可以定位出cpu过高的进程。在linux下top命令获得的进程号和jps工具获得的vmid是相同的 2用top -Hp命令查看线程的情况 可以看到是线程id为7287这个线程一直在占用cpu
3把线程号转换为16进制
[rootlocalhost ~]# printf %x 7287
1c77记下这个16进制的数字下面我们要用
4用jstack工具查看线程栈情况
[rootlocalhost ~]# jstack 7268 | grep 1c77 -A 10
http-nio-8080-exec-2 #16 daemon prio5 os_prio0 tid0x00007fb66ce81000 nid0x1c77 runnable [0x00007fb639ab9000]java.lang.Thread.State: RUNNABLEat com.spareyaya.jvm.service.EndlessLoopService.service(EndlessLoopService.java:19)at com.spareyaya.jvm.controller.JVMController.endlessLoop(JVMController.java:30)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)通过jstack工具输出现在的线程栈再通过grep命令结合上一步拿到的线程16进制的id定位到这个线程的运行情况其中jstack后面的7268是第1步定位到的进程号grep后面的是2、3步定位到的线程号。
从输出结果可以看到这个线程处于运行状态在执行com.spareyaya.jvm.service.EndlessLoopService.service这个方法代码行号是19行这样就可以去到代码的19行找到其所在的代码块看看是不是处于循环中这样就定位到了问题。
死锁
死锁并没有第一种场景那么明显web应用肯定是多线程的程序它服务于多个请求程序发生死锁后死锁的线程处于等待状态WAITING或TIMED_WAITING等待状态的线程不占用cpu消耗的内存也很有限而表现上可能是请求没法进行最后超时了。在死锁情况不多的时候这种情况不容易被发现。
可以使用jstack工具来查看
1jps查看java进程
[rootlocalhost ~]# jps -l
8737 sun.tools.jps.Jps
8682 jvm-0.0.1-SNAPSHOT.jar2jstack查看死锁问题
由于web应用往往会有很多工作线程特别是在高并发的情况下线程数更多于是这个命令的输出内容会十分多。jstack最大的好处就是会把产生死锁的信息包含是什么线程产生的输出到最后所以我们只需要看最后的内容就行了
Java stack information for the threads listed above:Thread-4:at com.spareyaya.jvm.service.DeadLockService.service2(DeadLockService.java:35)- waiting to lock 0x00000000f5035ae0 (a java.lang.Object)- locked 0x00000000f5035af0 (a java.lang.Object)at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$1(JVMController.java:41)at com.spareyaya.jvm.controller.JVMController$$Lambda$457/1776922136.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
Thread-3:at com.spareyaya.jvm.service.DeadLockService.service1(DeadLockService.java:27)- waiting to lock 0x00000000f5035af0 (a java.lang.Object)- locked 0x00000000f5035ae0 (a java.lang.Object)at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$0(JVMController.java:37)at com.spareyaya.jvm.controller.JVMController$$Lambda$456/474286897.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.发现了一个死锁原因也一目了然。
内存泄漏
我们都知道java和c的最大区别是前者会自动收回不再使用的内存后者需要程序员手动释放。在c中如果我们忘记释放内存就会发生内存泄漏。但是不要以为jvm帮我们回收了内存就不会出现内存泄漏。
程序发生内存泄漏后进程的可用内存会慢慢变少最后的结果就是抛出OOM错误。发生OOM错误后可能会想到是内存不够大于是把-Xmx参数调大然后重启应用。这么做的结果就是过了一段时间后OOM依然会出现。最后无法再调大最大堆内存了结果就是只能每隔一段时间重启一下应用。
内存泄漏的另一个可能的表现是请求的响应时间变长了。这是因为频繁发生的GC会暂停其它所有线程Stop The World造成的。
为了模拟这个场景使用了以下的程序
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {public static void main(String[] args) {Main main new Main();while (true) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}main.run();}}private void run() {ExecutorService executorService Executors.newCachedThreadPool();for (int i 0; i 10; i) {executorService.execute(() - {// do something...});}}
}运行参数是-Xms20m -Xmx20m -XX:PrintGC把可用内存调小一点并且在发生gc时输出信息运行结果如下
...
[GC (Allocation Failure) 12776K-10840K(18432K), 0.0309510 secs]
[GC (Allocation Failure) 13400K-11520K(18432K), 0.0333385 secs]
[GC (Allocation Failure) 14080K-12168K(18432K), 0.0332409 secs]
[GC (Allocation Failure) 14728K-12832K(18432K), 0.0370435 secs]
[Full GC (Ergonomics) 12832K-12363K(18432K), 0.1942141 secs]
[Full GC (Ergonomics) 14923K-12951K(18432K), 0.1607221 secs]
[Full GC (Ergonomics) 15511K-13542K(18432K), 0.1956311 secs]
...
[Full GC (Ergonomics) 16382K-16381K(18432K), 0.1734902 secs]
[Full GC (Ergonomics) 16383K-16383K(18432K), 0.1922607 secs]
[Full GC (Ergonomics) 16383K-16383K(18432K), 0.1824278 secs]
[Full GC (Allocation Failure) 16383K-16383K(18432K), 0.1710382 secs]
[Full GC (Ergonomics) 16383K-16382K(18432K), 0.1829138 secs]
[Full GC (Ergonomics) Exception in thread main 16383K-16382K(18432K), 0.1406222 secs]
[Full GC (Allocation Failure) 16382K-16382K(18432K), 0.1392928 secs]
[Full GC (Ergonomics) 16383K-16382K(18432K), 0.1546243 secs]
[Full GC (Ergonomics) 16383K-16382K(18432K), 0.1755271 secs]
[Full GC (Ergonomics) 16383K-16382K(18432K), 0.1699080 secs]
[Full GC (Allocation Failure) 16382K-16382K(18432K), 0.1697982 secs]
[Full GC (Ergonomics) 16383K-16382K(18432K), 0.1851136 secs]
[Full GC (Allocation Failure) 16382K-16382K(18432K), 0.1655088 secs]
java.lang.OutOfMemoryError: Java heap space可以看到虽然一直在gc占用的内存却越来越多说明程序有的对象无法被回收。但是上面的程序对象都是定义在方法内的属于局部变量局部变量在方法运行结果后所引用的对象在gc时应该被回收啊但是这里明显没有。
为了找出到底是哪些对象没能被回收我们加上运行参数-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathheap.bin意思是发生OOM时把堆内存信息dump出来。运行程序直至异常于是得到heap.dump文件然后我们借助eclipse的MAT插件来分析如果没有安装需要先安装。
然后File-Open Heap Dump… 然后选择刚才dump出来的文件选择Leak Suspects MAT会列出所有可能发生内存泄漏的对象 可以看到居然有21260个Thread对象3386个ThreadPoolExecutor对象如果你去看一下java.util.concurrent.ThreadPoolExecutor的源码可以发现线程池为了复用线程会不断地等待新的任务线程也不会回收需要调用其shutdown()方法才能让线程池执行完任务后停止。
其实线程池定义成局部变量好的做法是设置成单例。
上面只是其中一种处理方法
在线上的应用内存往往会设置得很大这样发生OOM再把内存快照dump出来的文件就会很大可能大到在本地的电脑中已经无法分析了因为内存不足够打开这个dump文件。这里介绍另一种处理办法
1用jps定位到进程号
C:\Users\spareyaya\IdeaProjects\maven-project\target\classes\org\example\netjps -l
24836 org.example.net.Main
62520 org.jetbrains.jps.cmdline.Launcher
129980 sun.tools.jps.Jps
136028 org.jetbrains.jps.cmdline.Launcher因为已经知道了是哪个应用发生了OOM这样可以直接用jps找到进程号135988
2用jstat分析gc活动情况
jstat是一个统计java进程内存使用情况和gc活动的工具参数可以有很多可以通过jstat -help查看所有参数以及含义
C:\Users\spareyaya\IdeaProjects\maven-project\target\classes\org\example\netjstat -gcutil -t -h8 24836 1000
Timestamp S0 S1 E O M CCS YGC YGCT FGC FGCT GCT29.1 32.81 0.00 23.48 85.92 92.84 84.13 14 0.339 0 0.000 0.33930.1 32.81 0.00 78.12 85.92 92.84 84.13 14 0.339 0 0.000 0.33931.1 0.00 0.00 22.70 91.74 92.72 83.71 15 0.389 1 0.233 0.622上面是命令意思是输出gc的情况输出时间每8行输出一个行头信息统计的进程号是24836每1000毫秒输出一次信息。
输出信息是Timestamp是距离jvm启动的时间S0、S1、E是新生代的两个Survivor和EdenO是老年代区M是MetaspaceCCS使用压缩比例YGC和YGCT分别是新生代gc的次数和时间FGC和FGCT分别是老年代gc的次数和时间GCT是gc的总时间。虽然发生了gc但是老年代内存占用率根本没下降说明有的对象没法被回收当然也不排除这些对象真的是有用。
3用jmap工具dump出内存快照
jmap可以把指定java进程的内存快照dump出来效果和第一种处理办法一样不同的是它不用等OOM就可以做到而且dump出来的快照也会小很多。
jmap -dump:live,formatb,fileheap.bin 24836这时会得到heap.bin的内存快照文件然后就可以用eclipse来分析了。 总结
以上三种严格地说还算不上jvm的调优只是用了jvm工具把代码中存在的问题找了出来。我们进行jvm的主要目的是尽量减少停顿时间提高系统的吞吐量。
但是如果我们没有对系统进行分析就盲目去设置其中的参数可能会得到更坏的结果jvm发展到今天各种默认的参数可能是实验室的人经过多次的测试来做平衡的适用大多数的应用场景。
如果你认为你的jvm确实有调优的必要也务必要取样分析最后还得慢慢多次调节才有可能得到更优的效果。