这几种常见的 JVM 调优场景,你知道吗?

手机软件开发 2024-9-2 13:16:56 40 0 来自 中国
假定你已经相识了运行时的数据地区和常用的垃圾接纳算法,也相识了Hotspot支持的垃圾接纳器。
一、cpu占用过高

cpu占用过高要分环境讨论,是不是业务上在搞运动,突然有大批的流量进来,而且运动竣事后cpu占用率就降落了,假如是这种环境其实可以不消太关心,由于哀求越多,须要处理处罚的线程数越多,这是正常的征象。
话说返来,假如你的服务器配置本身就差,cpu也只有一个核心,这种环境,轻微多一点流量就真的可以或许把你的cpu资源耗尽,这时应该思量先把配置提升吧。
第二种环境,cpu占用率长期过高,这种环境下大概是你的步伐有那种循环次数超等多的代码,以致是出现死循环了。排查步调如下:
(1)用top下令检察cpu占用环境

如许就可以定位出cpu过高的进程。在linux下,top下令得到的进程号和jps工具得到的vmid是雷同的:
(2)用top -Hp下令检察线程的环境

3.png 可以看到是线程id为7287这个线程不停在占用cpu
(3)把线程号转换为16进制

[root@localhost ~]# printf "%x" 72871c77记下这个16进制的数字,下面我们要用
(4)用jstack工具检察线程栈环境

[root@localhost ~]# jstack 7268 | grep 1c77 -A 10"http-nio-8080-exec-2" #16 daemon prio=5 os_prio=0 tid=0x00007fb66ce81000 nid=0x1c77 runnable [0x00007fb639ab9000]   java.lang.Thread.State: RUNNABLE at 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工具来检察
(1)jps检察java进程

[root@localhost ~]# jps -l8737 sun.tools.jps.Jps8682 jvm-0.0.1-SNAPSHOT.jar(2)jstack检察死锁题目

由于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:HeapDumpPath=heap.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\net>jps -l24836 org.example.net.Main62520 org.jetbrains.jps.cmdline.Launcher129980 sun.tools.jps.Jps136028 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\net>jstat -gcutil -t -h8 24836 1000Timestamp         S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT           29.1  32.81   0.00  23.48  85.92  92.84  84.13     14    0.339     0    0.000    0.339           30.1  32.81   0.00  78.12  85.92  92.84  84.13     14    0.339     0    0.000    0.339           31.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和Eden,O是老年代区,M是Metaspace,CCS使用压缩比例,YGC和YGCT分别是新生代gc的次数和时间,FGC和FGCT分别是老年代gc的次数和时间,GCT是gc的总时间。虽然发生了gc,但是老年代内存占用率根本没降落,阐明有的对象没法被接纳(固然也不扫除这些对象真的是有效)。
(3)用jmap工具dump出内存快照

jmap可以把指定java进程的内存快照dump出来,结果和第一种处理处罚办法一样,不同的是它不消等OOM就可以做到,而且dump出来的快照也会小许多。
jmap -dump:live,format=b,file=heap.bin 24836这时会得到heap.bin的内存快照文件,然后就可以用eclipse来分析了。
四、总结

以上三种严格地说还算不上jvm的调优,只是用了jvm工具把代码中存在的题目找了出来。我们举行jvm的重要目的是只管镌汰停顿时间,进步体系的吞吐量。
但是假如我们没有对体系举行分析就盲目去设置此中的参数,大概会得到更坏的结果,jvm发展到本日,各种默认的参数大概是实行室的人颠末多次的测试来做均衡的,实用大多数的应用场景。
假如你以为你的jvm确实有调优的须要,也务须要取样分析,末了还得逐步多次调治,才有大概得到更优的结果。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-19 06:20, Processed in 0.186675 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表