这几种常见的 JVM 调优场景

2023/6/5 22:36:41

假定你已经了解了运行时的数据区域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器。

一、cpu占用过高

cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束后cpu占用率就下降了,如果是这种情况其实可以不用太关心,因为请求越多,需要处理的线程数越多,这是正常的现象。

话说回来,如果你的服务器配置本身就差,cpu也只有一个核心,这种情况,稍微多一点流量就真的能够把你的cpu资源耗尽,这时应该考虑先把配置提升吧。

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

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

img

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

img

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

img

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

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

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

记下这个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应用肯定是多线程的程序,它服务于多个请求,程序发生死锁后,死锁的线程处于等待状态(WAITINGTIMED_WAITING),等待状态的线程不占用cpu,消耗的内存也很有限,而表现上可能是请求没法进行,最后超时了。在死锁情况不多的时候,这种情况不容易被发现。

可以使用jstack工具来查看

(1)jps查看java进程

[root@localhost ~]# jps -l
8737 sun.tools.jps.Jps
8682 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

img

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

img

可以看到居然有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 -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\net>jstat -gcutil -t -h8 24836 1000
Timestamp         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是新生代的两个SurvivorEden,O是老年代区,M是Metaspace,CCS使用压缩比例,YGC和YGCT分别是新生代gc的次数和时间,FGCFGCT分别是老年代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确实有调优的必要,也务必要取样分析,最后还得慢慢多次调节,才有可能得到更优的效果。

以上就是本篇文章的全部内容,希望能对大家的学习有所帮助,评论区留下“管用",记得三联哦。 还有其他知识分享,欢迎拜访链接: 首页


http://www.jnnr.cn/a/369212.html

相关文章

zlmedaikit android编译

Windows 10 64bit Android Studio&#xff1a;Android Studio Electric Eel | 2022.1.1 Patch 2 NDK: android-ndk-r25c 1. 安装jdk 2. 打开http://ping.chinaz.com网站&#xff0c;输入dl.google.com地址&#xff0c;开始ping监测&#xff0c;选择一个时间最短的大陆IP地址&a…

C语言函数:内存函数memcpy()以及实现

C语言函数&#xff1a;内存函数memcpy() 引言&#xff1a; #define _CRT_SECURE_NO_WARNINGS#include <stdlib.h>int main() {int arr1[20] { 1,2,3,4,5,6,7,8,9 };int arr2[20] { 0 };strcpy(arr2, arr1);return 0; } strcpy函数&#xff1a;C语言函数&#xff1a;字…

Mac升级go版本(指定或最新)

升级流程 在Mac中对go版本的升级采用先卸载后安装的过程进行go版本升级&#xff08;或者回退&#xff09;。 卸载 在卸载前&#xff0c;先查看下当前的go版本&#xff1a; go version 删除 go 目录&#xff1a; sudo rm -rf /usr/local/go /usr/local/go/bin/go /etc/path…

CSS面试题

CSS面试题css画三角形&#xff1a;本质就是利用边框bordercss选择器优先级一个div&#xff0c;没有给高度和宽度&#xff0c;怎么水平垂直居中height与line-height的区别css画三角形&#xff1a;本质就是利用边框border 我们首先看一种情况&#xff1a; width:0px; height:0px…

数据库:Redis哨兵及cluster集群部署

一、redis数据库哨兵模式 目录 一、redis数据库哨兵模式 1、什么是哨兵模式 2、哨兵的作用 3、哨兵结构组成 4、哨兵故障转移机制 5、哨兵工作、切换原理 6、哨兵主节点选举原则 7、哨兵模式部署 二、redis数据库cluster集群 1、cluster集群优点、数据存储及同步方式…

【操作系统复习】第3章 处理机调度与死锁 3

死锁&#xff08;Deadlock&#xff09;&#xff1a;指多个进程在运行过程中因争夺资源而造成的一种僵局&#xff0c;当进程处于这种僵持状态时&#xff0c;若无外力作用&#xff0c;这些进程都将永远不能再向前推进。 对资源不加限制地分配可能导致进程间由于竞争资源而相互制约…

【Docker】1、Docker 基础知识随意介绍

文章目录一、什么是 Docker二、为什么要用 Docker 部署三、Ubuntu Docker 安装四、Dockerfile五、镜像5.1 镜像拉取5.2 镜像删除5.3 使用 docker save 将镜像保存成 tar 归档文件5.4 导入使用 docker save 导出的镜像5.5 使用 docker import 从归档文件中创建镜像5.6 将本地镜像…

CNStack 网络插件:hybridnet 的设计与实现

作者&#xff1a; 若禾 CNStack 是阿里云推出的一款开放的一站式企业级云原生技术中台。在异构的混合云基础设施上&#xff0c;对资源进行统一纳管和优化调度&#xff0c;以开放的、云原生的方式为平台及业务系统提供生产可用的产品及组件&#xff0c;帮助用户打造满足大规模、…

【Python小技巧】Anaconda环境下使用Notepad++运行python程序

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Anaconda、notepad是什么&#xff1f;二、配置过程1. 找到Python.exe2. 编辑运行命令3. 试运行一段代码4. 保存快捷方式总结前言 最近升级了电脑系统&#…

Qt音视频开发27-ffmpeg视频旋转显示

一、前言 用手机或者平板拍摄的视频文件,很可能是旋转的,比如分辨率是1280x720,确是垂直的,相当于分辨率变成了720x1280,如果不做旋转处理的话,那脑袋必须歪着看才行,这样看起来太难受,所以一定要想办法解析到视频的旋转角度,然后根据这个角度重新绘制。在窗体那边也…

《花雕学AI》05:令人惊奇的ChatGPT,一个能够与人类对话的人工智能

今天是周末&#xff0c;4月2日&#xff0c;早上五点就起床了&#xff0c;没有去打羽毛球。 我平时在手机上喜欢看今日头条&#xff0c;了解各种时事新闻&#xff0c;发现今年来频繁出现的单词就是&#xff1a;ChatGPT&#xff0c;通过简单搜索&#xff0c;我逐步接受了这个概念…

从零开始学OpenCV——图像灰度变换详解(线性与非线性变换)

文章目录图像灰度变化灰度变换介绍灰度线性变换灰度分段线性变换图像点运算灰度非线性变换线性点运算灰度的非线性变换&#xff1a;对数变换灰度的非线性变换&#xff1a;伽马变换灰度的非线性变换&#xff1a;对比拉伸灰度的非线性变换&#xff1a; S形灰度变换灰度的非线性变…

Spring Cloud Alibaba Sentinel

一、简介 官网&#xff1a; https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D Sentinel: 分布式系统的流量防卫兵 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级、系统…

HDFS Balancer负载均衡器

文章目录1、背景2、什么是平衡2.1 每个DataNode的利用率计算2.2 集群的利用率2.3 平衡3、hdfs balancer语法4、运行一个简单的balance案例4.1 设置平衡数据传输带宽4.2 执行banalce5、参考文档1、背景 当我们的hadoop集群运行了一段时间之后&#xff0c;各个DataNode上的数据分…

[LeetCode刷题] - LC006 ZigZag Conversion

题目描述 给定一个字符串&#xff0c;想让你用纵向zig-zag形式展开&#xff0c;然后在横向进行拼接输出最后的新字符串 LeetCode 006Can you solve this real interview question? - Level up your coding skills and quickly land a job. This is the best place to expand…

基于Java(SSM)的学生考勤管理系统

前端&#xff1a;jsp 框架&#xff1a;SSM 数据库&#xff1a;MySQL 有数据库文件&#xff0c;导入即可用 可作为毕设参考 资源路径&#xff1a; https://download.csdn.net/download/yw1990128/87630215 1.1功能需求 信息技术的广泛使用&#xff0c;传统的考勤管理已经不…

多传感器融合定位GNSS、IMU、Lidar、Camera

概述 1.纯激光雷达点云定位 2.融合GNSS的点云定位 3.融合IMU的点云定位 4.融合GNSS、IMU的点云定位 5.融合Camera的点云定位 参考AutoWare 或者 Apollo AutoWare&#xff1a; https://github.com/autowarefoundation/autoware_ai_perception Apollo&#xff1a; https://git…

关于uview2.0计步器最大值赋值问题

需求&#xff1a;当有设置最大值时&#xff0c;计步器则设置max为此数值&#xff0c;最小值1&#xff0c;加大最大值时即不能在往上增加&#xff0c;最大值设置为0则为不限&#xff1b;那么u-number-box的max就要根据设置的数值去判断 uview的u-number-box文档&#xff1a;htt…

oracle 低版本 11.2.0.1.0 创建DBlink访问 oracle 19c提示用户密码错误ORA-010107

由于导数据需要&#xff0c;在oracle 11.2.0.1创建dblink访问oracle19c数据库&#xff0c;提示用户密码错误。 当然不会存在创建是 用户密码错误的情况。 报错页面如下 后面猜想是由数据库版本太低问题导致&#xff0c; 11g版本信息如下&#xff1a; SELECT t.* FROM v$vers…

小白学Pytorch系列--Torch.nn API Quantized Functions(19)

小白学Pytorch系列–Torch.nn API Quantized Functions(19) 方法注释parametrizations.orthogonalparametrizations.spectral_normparametrize.register_parametrizationparametrize.remove_parametrizationsparametrize.cachedparametrize.is_parametrizedparametrize.Paramet…