0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

关于JVM的调优知识

我快闭嘴 来源:博客园 作者:我是满意吖 2022-09-14 14:54 次阅读


最近很多小伙伴跟我说,自己学了不少JVM的调优知识,但是在实际工作中却不知道何时对JVM进行调优。今天,我就为大家介绍几种JVM调优的场景。

在阅读本文时,假定大家已经了解了运行时的数据区域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器。

cpu占用过高

cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束后cpu占用率就下降了,如果是这种情况其实可以不用太关心,因为请求越多,需要处理的线程数越多,这是正常的现象。话说回来,如果你的服务器配置本身就差,cpu也只有一个核心,这种情况,稍微多一点流量就真的能够把你的cpu资源耗尽,这时应该考虑先把配置提升吧。

第二种情况,cpu占用率长期过高 ,这种情况下可能是你的程序有那种循环次数超级多的代码,甚至是出现死循环了。排查步骤如下:

(1)用top命令查看cpu占用情况

cdb877c8-33f3-11ed-ba43-dac502259ad0.png

这样就可以定位出cpu过高的进程。在linux下,top命令获得的进程号和jps工具获得的vmid是相同的:

cde7d252-33f3-11ed-ba43-dac502259ad0.png

(2)用top -Hp命令查看线程的情况

ce22f260-33f3-11ed-ba43-dac502259ad0.png

可以看到是线程id为7287这个线程一直在占用cpu

(3)把线程号转换为16进制

[root@localhost~]#printf"%x"7287
1c77

记下这个16进制的数字,下面我们要用

(4)用jstack工具查看线程栈情况

[root@localhost~]#jstack7268|grep1c77-A10
"http-nio-8080-exec-2"#16daemonprio=5os_prio=0tid=0x00007fb66ce81000nid=0x1c77runnable[0x00007fb639ab9000]
java.lang.Thread.State:RUNNABLE
atcom.spareyaya.jvm.service.EndlessLoopService.service(EndlessLoopService.java:19)
atcom.spareyaya.jvm.controller.JVMController.endlessLoop(JVMController.java:30)
atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)
atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
atjava.lang.reflect.Method.invoke(Method.java:498)
atorg.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
atorg.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
atorg.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行,找到其所在的代码块,看看是不是处于循环中,这样就定位到了问题。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

死锁

死锁并没有第一种场景那么明显,web应用肯定是多线程的程序,它服务于多个请求,程序发生死锁后,死锁的线程处于等待状态(WAITING或TIMED_WAITING),等待状态的线程不占用cpu,消耗的内存也很有限,而表现上可能是请求没法进行,最后超时了。在死锁情况不多的时候,这种情况不容易被发现。

可以使用jstack工具来查看

(1)jps查看java进程

[root@localhost~]#jps-l
8737sun.tools.jps.Jps
8682jvm-0.0.1-SNAPSHOT.jar

(2)jstack查看死锁问题

由于web应用往往会有很多工作线程,特别是在高并发的情况下线程数更多,于是这个命令的输出内容会十分多。jstack最大的好处就是会把产生死锁的信息(包含是什么线程产生的)输出到最后,所以我们只需要看最后的内容就行了

Javastackinformationforthethreadslistedabove:
===================================================
"Thread-4":
atcom.spareyaya.jvm.service.DeadLockService.service2(DeadLockService.java:35)
-waitingtolock<0x00000000f5035ae0>(ajava.lang.Object)
-locked<0x00000000f5035af0>(ajava.lang.Object)
atcom.spareyaya.jvm.controller.JVMController.lambda$deadLock$1(JVMController.java:41)
atcom.spareyaya.jvm.controller.JVMController$$Lambda$457/1776922136.run(UnknownSource)
atjava.lang.Thread.run(Thread.java:748)
"Thread-3":
atcom.spareyaya.jvm.service.DeadLockService.service1(DeadLockService.java:27)
-waitingtolock<0x00000000f5035af0>(ajava.lang.Object)
-locked<0x00000000f5035ae0>(ajava.lang.Object)
atcom.spareyaya.jvm.controller.JVMController.lambda$deadLock$0(JVMController.java:37)
atcom.spareyaya.jvm.controller.JVMController$$Lambda$456/474286897.run(UnknownSource)
atjava.lang.Thread.run(Thread.java:748)

Found1deadlock.

发现了一个死锁,原因也一目了然。

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

内存泄漏

我们都知道,java和c++的最大区别是前者会自动收回不再使用的内存,后者需要程序员手动释放。在c++中,如果我们忘记释放内存就会发生内存泄漏。但是,不要以为jvm帮我们回收了内存就不会出现内存泄漏。

程序发生内存泄漏后,进程的可用内存会慢慢变少,最后的结果就是抛出OOM错误。发生OOM错误后可能会想到是内存不够大,于是把-Xmx参数调大,然后重启应用。这么做的结果就是,过了一段时间后,OOM依然会出现。最后无法再调大最大堆内存了,结果就是只能每隔一段时间重启一下应用。

内存泄漏的另一个可能的表现是请求的响应时间变长了。这是因为频繁发生的GC会暂停其它所有线程(Stop The World)造成的。

为了模拟这个场景,使用了以下的程序

importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
publicclassMain{
publicstaticvoidmain(String[]args){
Mainmain=newMain();
while(true){
try{
Thread.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
main.run();
}
}

privatevoidrun(){
ExecutorServiceexecutorService=Executors.newCachedThreadPool();
for(inti=0;i< 10;i++){
executorService.execute(()->{
//dosomething...
});
}
}
}

运行参数是-Xms20m -Xmx20m -XX:+PrintGC,把可用内存调小一点,并且在发生gc时输出信息,运行结果如下

...
[GC(AllocationFailure)12776K->10840K(18432K),0.0309510secs]
[GC(AllocationFailure)13400K->11520K(18432K),0.0333385secs]
[GC(AllocationFailure)14080K->12168K(18432K),0.0332409secs]
[GC(AllocationFailure)14728K->12832K(18432K),0.0370435secs]
[FullGC(Ergonomics)12832K->12363K(18432K),0.1942141secs]
[FullGC(Ergonomics)14923K->12951K(18432K),0.1607221secs]
[FullGC(Ergonomics)15511K->13542K(18432K),0.1956311secs]
...
[FullGC(Ergonomics)16382K->16381K(18432K),0.1734902secs]
[FullGC(Ergonomics)16383K->16383K(18432K),0.1922607secs]
[FullGC(Ergonomics)16383K->16383K(18432K),0.1824278secs]
[FullGC(AllocationFailure)16383K->16383K(18432K),0.1710382secs]
[FullGC(Ergonomics)16383K->16382K(18432K),0.1829138secs]
[FullGC(Ergonomics)Exceptioninthread"main"16383K->16382K(18432K),0.1406222secs]
[FullGC(AllocationFailure)16382K->16382K(18432K),0.1392928secs]
[FullGC(Ergonomics)16383K->16382K(18432K),0.1546243secs]
[FullGC(Ergonomics)16383K->16382K(18432K),0.1755271secs]
[FullGC(Ergonomics)16383K->16382K(18432K),0.1699080secs]
[FullGC(AllocationFailure)16382K->16382K(18432K),0.1697982secs]
[FullGC(Ergonomics)16383K->16382K(18432K),0.1851136secs]
[FullGC(AllocationFailure)16382K->16382K(18432K),0.1655088secs]
java.lang.OutOfMemoryError:Javaheapspace

可以看到虽然一直在gc,占用的内存却越来越多,说明程序有的对象无法被回收。但是上面的程序对象都是定义在方法内的,属于局部变量,局部变量在方法运行结果后,所引用的对象在gc时应该被回收啊,但是这里明显没有。

为了找出到底是哪些对象没能被回收,我们加上运行参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.bin,意思是发生OOM时把堆内存信息dump出来。运行程序直至异常,于是得到heap.dump文件,然后我们借助eclipse的MAT插件来分析,如果没有安装需要先安装。

然后File->Open Heap Dump... ,然后选择刚才dump出来的文件,选择Leak Suspects

cedb8212-33f3-11ed-ba43-dac502259ad0.png

MAT会列出所有可能发生内存泄漏的对象

cef87822-33f3-11ed-ba43-dac502259ad0.png

可以看到居然有21260个Thread对象,3386个ThreadPoolExecutor对象,如果你去看一下java.util.concurrent.ThreadPoolExecutor的源码,可以发现线程池为了复用线程,会不断地等待新的任务,线程也不会回收,需要调用其shutdown()方法才能让线程池执行完任务后停止。

其实线程池定义成局部变量,好的做法是设置成单例。

上面只是其中一种处理方法

在线上的应用,内存往往会设置得很大,这样发生OOM再把内存快照dump出来的文件就会很大,可能大到在本地的电脑中已经无法分析了(因为内存不足够打开这个dump文件)。这里介绍另一种处理办法:

(1)用jps定位到进程号

C:UsersspareyayaIdeaProjectsmaven-project	argetclassesorgexample
et>jps-l
24836org.example.net.Main
62520org.jetbrains.jps.cmdline.Launcher
129980sun.tools.jps.Jps
136028org.jetbrains.jps.cmdline.Launcher

因为已经知道了是哪个应用发生了OOM,这样可以直接用jps找到进程号135988

(2)用jstat分析gc活动情况

jstat是一个统计java进程内存使用情况和gc活动的工具,参数可以有很多,可以通过jstat -help查看所有参数以及含义

C:UsersspareyayaIdeaProjectsmaven-project	argetclassesorgexample
et>jstat-gcutil-t-h8248361000
TimestampS0S1EOMCCSYGCYGCTFGCFGCTGCT
29.132.810.0023.4885.9292.8484.13140.33900.0000.339
30.132.810.0078.1285.9292.8484.13140.33900.0000.339
31.10.000.0022.7091.7492.7283.71150.38910.2330.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.bin24836

这时会得到heap.bin的内存快照文件,然后就可以用eclipse来分析了。

总结

以上三种严格地说还算不上jvm的调优,只是用了jvm工具把代码中存在的问题找了出来。我们进行jvm的主要目的是尽量减少停顿时间,提高系统的吞吐量。

但是如果我们没有对系统进行分析就盲目去设置其中的参数,可能会得到更坏的结果,jvm发展到今天,各种默认的参数可能是实验室的人经过多次的测试来做平衡的,适用大多数的应用场景。

如果你认为你的jvm确实有调优的必要,也务必要取样分析,最后还得慢慢多次调节,才有可能得到更优的效果。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10456

    浏览量

    206607
  • JVM
    JVM
    +关注

    关注

    0

    文章

    152

    浏览量

    12130

原文标题:几种常见的JVM调优场景(建议收藏)

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    哪位有关于方面的知识,本人在做航模,想好好学学...

    哪位大大有关于方面的资料‘?/发给我一份呗。。我最近在做航模方面的东西,想认真学学电这方面的知识。。我Q783339844.。真是十分感谢啦
    发表于 07-30 08:53

    java开发人员不了解jvm对工作有影响吗

    作为一名java开发人员,不了解jvm对工作有什么影响?
    发表于 04-10 11:57

    HBase性能概述

    HBase性能
    发表于 07-03 11:35

    JVM性能指标分析

    JVM性能实践——JVM
    发表于 10-17 15:00

    flume读取文件延迟说明

    flume读取文件延迟
    发表于 07-17 16:38

    Jvm的整体结构和特点

    中都能够实现。在计算机中创建虚拟机时,需要将实体机的部分硬盘和内存容量作为虚拟机的硬盘和内存容量。每个虚拟机都有独立的CMOS、硬盘和操作系统,可以像使用实体机一样对虚拟机进行操作。  2、JVM
    发表于 01-05 17:23

    JVM知识点汇总,不看肯定后悔

    JVM知识点汇总,不看肯定后悔
    发表于 11-30 06:13

    功耗时经常用到的几个方法

    前言不清楚当前产品的整机功耗,就不清楚怎么获取产品的整机及各个模块的功耗数据,需要测量正确的功耗测量方法,快速的了解整机的功耗分布,为功耗提供方向。功耗测量功耗拆解,是功耗
    发表于 12-21 06:31

    关于linux系统ulimit的相关资料分享

    1、linux ulimit介绍在 Linux 系统中,在每个进程中都有一组资源限制,进程默认打开的最大文件数个数为 1024 个,可以通过如下配置查看:在应用程序开发过程中,比如向
    发表于 06-27 17:56

    基于全HDD aarch64服务器的Ceph性能实践总结

    和成本之间实现了最佳平衡,可以作为基于arm服务器来部署存储的参考设计。2 Ceph架构3 测试集群硬件配置:3台arm服务器每台arm服务器:软件配置性能测试工具4 方式4.1 硬件
    发表于 07-05 14:26

    KeenTune的算法之心——KeenOpt 算法框架 | 龙蜥技术

    文/KeenTune SIGKeenTune(轻豚)是一款 AI 算法与专家知识库双轮驱动的操作系统全栈式智能优化产品,为主流的操作系统提供轻量化、跨平台的一键式性能,让应用在智能定制的运行环境
    发表于 10-28 10:36

    JVM是什么?JVM虚拟机的栈执行原理详细概述

    JVM可以说离我们既熟悉又陌生,很多朋友可能在工作中接触不到这块技术,但是在面试往往被问到(概率还蛮大),被问到了自认倒霉,死记硬背是没用的,到头来还是的忘,今天给大家说道说道JVM知识点,我要没让你明白算我输,你可以留言喷我,
    的头像 发表于 02-20 13:50 2624次阅读
    <b class='flag-5'>JVM</b>是什么?<b class='flag-5'>JVM</b>虚拟机的栈执行原理详细概述

    Java:JVM虚拟机的入门知识

    Java开发现在面试越来越难了,进大厂必备的JVM、多线程高并发这都是最基础的知识了,今天我们一起来学习Java虚拟机入门。
    的头像 发表于 07-01 11:43 2074次阅读
    Java:<b class='flag-5'>JVM</b>虚拟机的入门<b class='flag-5'>知识</b>

    JVM知识体系剖析

    从源码到运行、类加载,再到内存分配和垃圾回收,以及JVM调优的技巧与实战。 理论-实战-面试三结合,带大家剖析整个JVM知识体系,一站解决JVM问题。 1、Oracle Java SE
    的头像 发表于 10-10 11:37 236次阅读
    <b class='flag-5'>JVM</b><b class='flag-5'>知识</b>体系剖析

    jvm参数的设置和jvm调优

    JVM(Java虚拟机)参数的设置和调优对于提高Java应用程序的性能和稳定性非常重要。在本文中,我们将详细介绍JVM参数的设置和调优方法。 一、JVM参数的设置 内存参数: -Xms:设置J
    的头像 发表于 12-05 11:36 508次阅读