几种常见的 JVM 调优场景

news2024/12/24 9:47:47

一、cpu占用过高

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

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

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

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

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

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

可以看到是线程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

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 -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.coloradmin.cn/o/384281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Java·算法·困难]LeetCode25. K 个一组翻转链表

每天一题&#xff0c;防止痴呆题目示例分析思路1题解1分析思路2题解2分析思路3题解3&#x1f449;️ 力扣原文 题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。…

JavaWeb--用户注册登录案例

用户注册登录案例1 用户登录1.1 需求分析1.2 环境准备1.3 代码实现2 用户注册2.1 需求分析2.2 代码编写3 SqlSessionFactory工具类抽取目标&#xff1a; 能够完成用户登录注册案例的实现能够完成SqlSessionFactory工具类的抽取 接下来我们通过两个比较常见的案例&#xff0c;一…

国家能源局持续发文,赛宁网安以实力响应电力网络靶场建设

​​一、政策导向 ​​国家能源局连续三年发文 推进电力网络靶场建设 2021.1. 国家能源局印发《2021电力安全监管重点工作任务》提出&#xff1a;加强电网及网络安全监管。推进电力行业网络安全仿真验证环境&#xff08;靶场&#xff09;建设&#xff0c;组织开展电力行业网…

如何构建一个稳定、可靠、安全的大型工业机械远程监控系统?

在工业机械设备的应用过程中&#xff0c;对于生产的质量、效率等要求都非常高&#xff0c;尤其是大型设备往往需要多人协同操作&#xff0c;如果没有实时的数据反馈和监控系统进行数据处理&#xff0c;就会出现数据偏差甚至错误现象。当前&#xff0c;国内大型工业机械设备数量…

AB测试-A/B Test

网络上有很多类似名称&#xff0c;又名A/B试验&#xff0c;ab test。 文章目录一、应用场景二、什么是AB测试三、AB测试可以解决什么问题四、AB测试的流程五、AB测试常见的误区六、AB测试的原理一、应用场景 以公司遇到的问题及需求入手&#xff0c;帮助大家建立感性认识。总结…

动态规划:leetcode 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

leetcode 198.打家劫舍leetcode 213.打家劫舍IIleetcode 337.打家劫舍IIIleetcode 198.打家劫舍你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相…

GitCode使用教程

目录 0.前言 1. 安装git 2. 注册 gitcode 3. 创建SSH key 4. GitCode 中配置公钥 5. 新建项目 6. clone项目 7. push 项目 0.前言 笔者之前大致知道&#xff0c;2020年9月10日&#xff0c;CSDN正式推出全新升级的开源平台 GitCode&#xff0c;不过鉴于稳定性原因一直没…

第十章 优化stop功能

优化stop功能 突然发现其实stop存在边缘案例是没有通过的&#xff0c;比如我图中红框中的obj.prop 当加上这句代码后&#xff0c;运行测试案例是不能通过的&#xff0c;原因分析&#xff1a; stop后已经把依赖清除了&#xff0c;obj.prop又走了get&#xff0c;触发了依赖收集…

Java 获取文件后缀名【一文总结所有方法】

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Delphi 中 FireDAC 数据库连接(管理事务)

参见&#xff1a;Delphi 中 FireDAC 数据库连接&#xff08;总览&#xff09;描述了如何使用FireDAC来管理DBMS事务。FireDAC提供TFDConnection和TFDTransaction组件来帮助你处理数据库事务。一、概述默认情况下&#xff0c;FireDAC应用程序在自动提交模式下工作&#xff0c;当…

Spring Cloud Alibaba全家桶(四)——微服务调用组件Feign

前言 本文小新为大家带来 微服务调用组件Feign 的相关知识&#xff0c;具体内容包含什么是Feign&#xff0c;Spring Cloud Alibaba快速整合OpenFeign&#xff0c;Spring Cloud Feign的自定义配置及使用&#xff08;包括&#xff1a;日志配置、契约配置、自定义拦截器实现认证逻…

五方面提高销售流程管理的CRM系统

销售充满了不确定性&#xff0c;面对不同的客户&#xff0c;销售人员需要采用不同的销售策略。也正因为这种不确定性&#xff0c;规范的销售流程对企业尤为重要&#xff0c;它会让销售工作更加有效&#xff0c;快速地实现成交。下面小编给您推荐个不错的CRM销售流程管理系统。 …

BQ25071QWDQCRQ1示意图ISO6721BQDRQ1引脚配置ISO7330CQDWRQ1数字隔离器

1、BQ25071QWDQCRQ1应用程序示意图BQ25071 1A 单输入、单节线性电池充电器是一款高度集成的线性 LiFePO4 电池充电器&#xff0c;适用于空间受限的便携式应用。它接受来自 USB 端口或交流适配器的电力&#xff0c;为单节 LiFePO4 电池提供高达 1A 的充电电流。该器件具有单个电…

【DaVinci Developer专题】-45-自动生成SWC中所有Runnable对应的C文件

点击返回「Autosar从入门到精通-实战篇」总目录 案例背景(共5页精讲): 在DaVinci Developer中,以Test_A_SWC的Runnable为例,见图0-1。我们现在尝试自动生成一个包含Test_A_SWC_Init和Test_A_SWC_Main函数原型(也是适用于 C/S Port Serve Runnable)的C文件。 图0-1 目…

详解旨在提升EVM底层性能的兼容公链Monad

EVM带来的繁荣2020年以太坊链上DeFi的蓬勃发展使得EVM成为关注焦点&#xff0c;大部分DeFi项目都开始基于以太坊公链&#xff0c;这也使得EVM成为行业的标杆&#xff0c;不少链都加入了EVM大军&#xff0c;比如polygon、BSC、fantom等等&#xff0c;而EVM也使得链上生态进一步繁…

【算法笔记】队列与优先队列

队列与优先队列 1.队列的概念 只允许在一端插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff1b;进行插入操作的一端称为队尾&#xff08;入队列&#xff09;&#xff0c;进行删除操作的一端称为队头&#xff08;出队列&#xff09;&#xff1b; 队列…

离散事件动态系统

文章目录离散事件动态系统ppt离散事件系统建模离散事件动态系统的基本组成元素离散事件动态系统仿真具体建模petri建模实例离散事件动态系统 ppt ppt 仿真建模步骤 离散事件系统建模 from&#xff1a;离散事件系统建模 离散事件动态系统的基本组成元素 &#xff08;1&am…

腾讯云服务器CVM和轻量应用服务器区别对比

腾讯云新推出的轻量应用服务器Lighthouse和原来的CVM云服务器有什么区别&#xff1f;轻量应用服务器Lighthouse是一种易于使用和管理、适合承载轻量级业务负载的云服务器&#xff0c;主要用于Web网站应用&#xff0c;轻量服务器使用及后期运维更加简单方便&#xff1b;云服务器…

TypeError: Axes3D.view_init() got an unexpected keyword argument ‘roll‘——版本问题

解决办法 删除roll关键字参数。 掉坑过程 今天在学习matplotlib绘制3D图形时&#xff0c;使用的是matplotlib官网提供的例子Plot 2D data on 3D plot 敲好代码后&#xff0c;运行时报错TypeError: Axes3D.view_init() got an unexpected keyword argument ‘roll’ 看来官…

k8s学习之路 | Day18 Pod 基础

文章目录Pod 基础认知什么是 PodPod 的示例 yamlPod 的形式Pod 的多容器协同Pod 的生命周期容器的类型应用容器初始化容器临时容器静态 Pod什么是静态 Pod静态 Pod 位置Pod 探针机制探针类型Probe 配置项探针案例Pod 基础认知 什么是 Pod https://kubernetes.io/zh-cn/docs/c…