JVM日常故障排查小结

前置知识

jstack简介

jstackJVM自带的工具,用于追踪Java进程线程id的堆栈信息、锁信息,或者打印core file,远程调试Java堆栈信息等。

而我们常用的指令则是下面这条:

# 打印对应java进程的堆栈信息
jstack [ option ] pid 

option常见选项

-F	当正常输出的请求不被响应时,强制输出线程堆栈
-m	如果调用到本地方法的话,可以显示C/C++的堆栈
-l	除堆栈外,显示关于锁的附加信息,在发生死锁时可以用jstack -l pid来观察锁持有情况

Monitor锁工作机制

这个知识涉及到了Java锁底层的工作原理,感兴趣的读者可以参阅笔者这篇文章的Synchronized 是怎样实现的 这一小节

聊聊Java关键字synchronized

线程状态复习

在使用jstack排查问题之前,我们必须了解堆栈中的信息,所以我们首先必须复习一下线程中的六大状态:

  1. New:线程处于创建但还未启动的状态。
  2. RUNNABLE:RUNNABLE其实是JVM自定义的一种状态,如果和操作系统的线程状态进行等价理解的话,RUNNABLE是处于操作系统Running或者Ready状态,因为CPU在这两个状态间的切换几乎是瞬时的,所以JVM统一用RUNNABLE表示。
  3. Waiting:线程处于等待唤醒状态。
  4. Timed Waiting:在有限时间内线程等待唤醒。
  5. Blocked:程序等待进入同步区域,等待监视器锁中,线程处于阻塞状态。
  6. Terminated:线程工作完成,处于结束状态了。

了解过线程状态后,我们就可以了解一下jstack导出的dump文件中线程会基于这些状态出现的各种情况:


runnable:线程处于执行中
deadlock:死锁(重点关注)
blocked:线程被阻塞 (重点关注)
Parked:停止
locked:对象加锁
waiting:线程正在等待
waiting to lock:等待上锁
Object.wait():对象等待中
waiting for monitor entry:等待获取监视器(重点关注)
Waiting on condition:等待资源(重点关注),最常见的情况是线程在等待网络的读写


MAT(Memory Analyzer)下载安装

下载地址

https://www.eclipse.org/mat/previousReleases.php

为了后续我们可以查看JVM输出的hprof日志,我们需要下载一个MAT的工具,如下图所示,选择更早版本

在这里插入图片描述

以笔者为例,笔者就选择了1.7版本

在这里插入图片描述

完成下载后,双击下面这个exe文件能打开就说明一切正常

在这里插入图片描述

线程死锁问题排查思路

问题代码

如下所示,笔者使用spring boot写了一段死锁的代码,如下所示,然后将其放到服务器中启动

@RestController
public class TestController {

    private static Logger logger = LoggerFactory.getLogger(TestController.class);

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    /**
     * 模拟一个线程死锁的请求
     *
     * @return
     */
    @GetMapping("deadLock")
    public String deadLock() throws Exception {

        Thread t1 = new Thread(() -> {
            logger.info("线程1开始工作,先获取锁1");
            synchronized (lock1) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("线程1获得锁1,尝试获得锁2");

                synchronized (lock2) {
                    logger.info("线程1获得锁2成功");
                }
            }

        });

        Thread t2 = new Thread(() -> {
            logger.info("线程2开始工作,先获取锁2");
            synchronized (lock2) {

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    logger.info("线程2获得锁1成功");
                }
            }


        });

        t1.setName("my-thread-1");
        t2.setName("my-thread-2");
        t1.join();
        t2.join();
        t1.start();
        t2.start();


        return "success";
    }
}

重现问题

由于这只是一个demo,我们日常发现这种问题的时候大概率是多线程中的业务没有结束,所以重现问题也很简单,通过命令调用一下接口即可

curl http://localhost:8888/deadLock

排查思路

首先确定当前发生死锁的java应用,我们通过jps确定进程id,可以看到笔者服务器的进程id23334


[root@xxxxtmp]# jps
23830 Jps
23334 jar

然后通过jstack -l查看锁以及锁的附加信息

jstack -l 23334

最终可以在jstack的最下方看到这样一段信息(Found one Java-level deadlock),由此确认出现my-thread-1持有0x00000000ec509610等待0x00000000ec509620my-thread-2反之。

然后我们通过jstack信息即可定位到问题代码在TestController.java:53以及TestController.java:37

Found one Java-level deadlock:
=============================
"my-thread-2":
  waiting to lock monitor 0x00007f2800ac9318 (object 0x00000000ec509610, a java.lang.Object),
  which is held by "my-thread-1"
"my-thread-1":
  waiting to lock monitor 0x00007f27e40062c8 (object 0x00000000ec509620, a java.lang.Object),
  which is held by "my-thread-2"

Java stack information for the threads listed above:
===================================================
"my-thread-2":
        at com.example.jstackTest.TestController.lambda$deadLock$1(TestController.java:53)
        - waiting to lock <0x00000000ec509610> (a java.lang.Object)
        - locked <0x00000000ec509620> (a java.lang.Object)
        at com.example.jstackTest.TestController$$Lambda$582/2089009876.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"my-thread-1":
        at com.example.jstackTest.TestController.lambda$deadLock$0(TestController.java:37)
        - waiting to lock <0x00000000ec509620> (a java.lang.Object)
        - locked <0x00000000ec509610> (a java.lang.Object)
        at com.example.jstackTest.TestController$$Lambda$581/1994255298.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

CPU 飙升问题排查思路

简介

导致CPU 100%的原因有很多,一般来说都是编码不当导致的,所以常规的排查思路为:

  1. 定位进程号,如果是Java进程则查看是哪个线程导致的。
  2. 定位导致CPU 飙升的线程号,转为16进制。
  3. 导致JVM锁信息日志,使用线程号定位代码。
  4. 排查并修复代码问题。

问题复现

首先笔者准备了一个导致CPU飙升的问题代码,可以看到线程池中的线程不会停止不断工作

private ExecutorService threadPool = Executors.newFixedThreadPool(100);
    private static Object lock = new Object();
    private static Logger logger = LoggerFactory.getLogger(TestController.class);

    public TestController() {
    }

    @GetMapping({"/test"})
    public void test() {
        for(int i = 0; i < 100; ++i) {
            this.threadPool.execute(() -> {
                logger.info("加法线程开始工作");
                long sum = 0L;
                Object var2 = lock;
                synchronized(lock){}

                try {
                    while(true) {
                        sum += 0L;
                    }
                } finally {
                    ;
                }
            });
        }

    }

然后我们发起请求

 curl http://localhost:9550/test

排查过程

此时使用top命令查看,可以看到24411号进程CPU占用百分比飙升。此时我们就需要进一步定位这个进程的哪一个线程出问题了。

在这里插入图片描述

所以我们需要进一步定位这个问题是哪一个线程导致的,命令如下所示,使用线程模式查看对应pid的线程情况

top -Hp 24411

可以看到25321这个线程CPU占用过高,此时我们就可以通过thread dump定位导致问题的代码段

在这里插入图片描述

键入jstack -l 24411 >/tmp/log.txt到处日志,然后将线程号25321转为16进制,这里笔者使用了一个在线的网站地址

https://www.sojson.com/hexconvert.html

可以看到25321转换为16进制值为62e9,所以我们就使用62e9到导出的日志文件中查看这个线程堆栈情况。

在这里插入图片描述

使用转换的值从刚刚导出的日志中定位,可以看到该线程处于运行状态,很明显这个线程一直处于运行中,有一段逻辑肯定在不停的消耗CPU资源,所以我们查看代码位置在TestController.java:32,由此得到问题代码并修复问题。

在这里插入图片描述

OOM问题排查思路

问题简述

出现OOM问题大抵是有两个原因:

  1. 大流量导致服务器创建大量的对象把内存打爆了,面对这种情况我们除了熔断以外别无他法。
  2. 程序编写不规范导致,大流量情况下出现垃圾内存进而出现OOM,笔者本地探讨的就是这种情况。

复现问题

如下所示,笔者初始化了一个Spring Boot程序,创建一个线程池,模拟无数个线程池将不断将内存写入4M的数据,并且不清理。

RestController
public class TestController {
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 100, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());// 创建线程池,通过线程池,保证创建的线程存活

    final static ThreadLocal<Byte[]> localVariable = new ThreadLocal<Byte[]>();// 声明本地变量

    (value = "/test0")
    public String test0(HttpServletRequest request) {
        poolExecutor.execute(() -> {
            Byte[] c = new Byte[4* 1024* 1024];
            localVariable.set(c);// 为线程添加变量

        });
        return "success";
    }

   
}

完成后部署到服务器上,并使用以下命令启动,可以看到笔者调整的jvm堆内存大小(笔者服务器内存为1g故这里设置为100m),以及设置OOM输出参数

java -jar -Xms100m -Xmx100m # 调整堆内存大小
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof  # 表示发生OOM时输出日志文件,指定path为/tmp/heapdump.hprof
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/tmp/heapTest.log # 打印日志、gc时间以及指定gc日志的路径
demo-0.0.1-SNAPSHOT.jar

完成后我们启动项目使用API post进行并发请求(笔者本次堆区设置很小,所以在服务器中curl一样可以重现问题)

在这里插入图片描述

可以看到服务器不久之后就出现了OOM问题

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.8)

2023-01-28 23:42:39.579  INFO 3721 --- [           main] c.e.jstackTest.JstackTestApplication     : Starting JstackTestApplication v0.0.1-SNAPSHOT using Java 1.8.0_202 on iZ8vb7bhe4b8nhhhpavhwpZ with PID 3721 (/tmp/jstackTest-0.0.1-SNAPSHOT.jar started by root in /tmp)
2023-01-28 23:42:39.588  INFO 3721 --- [           main] c.e.jstackTest.JstackTestApplication     : No active profile set, falling back to 1 default profile: "default"
2023-01-28 23:42:42.300  INFO 3721 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8888 (http)
2023-01-28 23:42:42.340  INFO 3721 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-01-28 23:42:42.340  INFO 3721 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.71]
2023-01-28 23:42:42.613  INFO 3721 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-01-28 23:42:42.615  INFO 3721 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2875 ms
2023-01-28 23:42:44.324  INFO 3721 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8888 (http) with context path ''
2023-01-28 23:42:44.351  INFO 3721 --- [           main] c.e.jstackTest.JstackTestApplication     : Started JstackTestApplication in 5.923 seconds (JVM running for 7.085)
2023-01-28 23:43:29.742  INFO 3721 --- [nio-8888-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-01-28 23:43:29.742  INFO 3721 --- [nio-8888-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-01-28 23:43:29.748  INFO 3721 --- [nio-8888-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /tmp/heapdump.hprof ...
Heap dump file created [151939570 bytes in 1.112 secs]
Exception in thread "pool-1-thread-5" java.lang.OutOfMemoryError: Java heap space
        at com.example.jstackTest.TestController.lambda$test0$0(TestController.java:25)
        at com.example.jstackTest.TestController$$Lambda$582/394910033.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-7" java.lang.OutOfMemoryError: Java heap space
        at com.example.jstackTest.TestController.lambda$test0$0(TestController.java:25)
        at com.example.jstackTest.TestController$$Lambda$582/394910033.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-9" java.lang.OutOfMemoryError: Java heap space

排查思路

然后笔者使用top定位,可以看到哪个堆区大小设置为100m的进程内存占用达到10%,很明显这个进程是有问题的。

在这里插入图片描述

然后使用top -Hp 3721查看进程的线程信息,可以看到这里面的每一个线程基本都把堆区内存打满了,我们不妨查看任意一个线程

在这里插入图片描述

我们首先使用jstack -l 3721将日志导出,这里就以3873转为16进制查看线程状态,可以发现线程处于等待状态,而且日志中并没有存在死锁的信息,所以我们必须进一步查看堆区情况确认是否是因为内存泄漏导致的。

在这里插入图片描述

然后使用jmap查看堆区使用情况

jmap -heap 3721

从下面的日志可以看出老年代使用率高达82%,很明显有一些长期没有释放的对象在内存中导致OOM问题。

在这里插入图片描述

我们从上文设置的oom日志路径中找到日志/tmp/heapdump.hprof,导出到本地,使用MAT打开

在这里插入图片描述

找到使用率最高的Byte数组,点击下图Histogram ,点击内存占用最高的选项展开。

在这里插入图片描述

这里补充一下截图中看到的两个选项:

  1. with incoming references: 表示的是 当前查看的对象,被外部的应用。
  2. with outGoing references: 表示的是 当前对象,引用了外部对象。

所以我们的选择with incoming reference

在这里插入图片描述

可以定位到就是我们一个线程池中的threadLocal使用不当导致OOM问题了

在这里插入图片描述

参考文献

Java内存分析工具MAT(Memory Analyzer Tool)安装使用实例

JVM参数-XX:+HeapDumpOnOutOfMemoryError使用方法

Java 内存限制

Java 性能调优实战

面渣逆袭(Java 虚拟机-JVM面试题八股文)必看👍

Java程序员必备:jstack命令解析

内存分析工具MAT的使用入门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/254889.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于JAVA的天然气工程运维系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

SpringBoot之三层架构的详细解析

3. 分层解耦 3.1 三层架构 3.1.1 介绍 在我们进行程序设计以及程序开发时&#xff0c;尽可能让每一个接口、类、方法的职责更单一些&#xff08;单一职责原则&#xff09;。 单一职责原则&#xff1a;一个类或一个方法&#xff0c;就只做一件事情&#xff0c;只管一块功能。…

测试用例的修改更新

测试用例的修改更新是指测试过程中由于用户需求的改变&#xff0c;或者测试过程中发现有新的需求产生&#xff0c;使得测试用例需要进行修改。修改更新测试用例不仅是一种测试技术&#xff0c;更是一种质量保证的方法。但修改和更新测试用例的技术要点在于&#xff1a; 1、执行…

【数据结构】八大排序之直接插入排序算法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.直接插入排序简介及思路 直接插入排序(Straight Insertion Sort)是一种简单直观的插入排序算法. 它的基本操作是: 将一个数据插入到已经排好的有序表中,从而得到一个新的,数…

Spring 原理(一)

Spring 原理 它是一个全面的、企业应用开发一站式的解决方案&#xff0c;贯穿表现层、业务层、持久层。但是 Spring仍然可以和其他的框架无缝整合。 Spring 特点 轻量级控制反转面向切面容器框架集合 Spring 核心组件 Spring 常用模块 Spring 主要包 Spring 常用注解 bean …

【期末复习向】走进循环神经网络系列-RNN,LSTM,GRU

RNN 上篇文章复习了最简单的神经网络MLP&#xff0c;它是由输入层&#xff0c;隐藏层和输出层构成的。当然这也是所有神经网络最基本的架构。但是MLP过于简单&#xff0c;存在的问题之一就是无法考虑全局的信息&#xff0c;也就是前后输入的信息&#xff0c;这对于解决时间序列…

【操作系统】实验四 进程调度

实验名称&#xff1a; 实验四 进程调度 实验目的&#xff1a; 1. 加深理解有关进程控制块、进程队列的概念 2. 体会和了解优先级和时间片轮转调度算法的具体实施办法 实验内容&#xff1a; 1. 设计进程控制块 PCB 表结构&#xff08;与实验一的结构相同&#xff09;&#xff…

【倒立摆】仿真、起摆

建模 在此示例中&#xff0c;我们将考虑带有推车的倒立摆系统的二维版本&#xff0c;其中摆锤位于 被限制在垂直平面内移动&#xff0c;如下图所示。对于该系统&#xff0c;控制输入是水平移动小车的力 F F F&#xff0c;输出是摆的角位置KaTeX parse error: Undefined contro…

计算机网络:数据链路层(广域网、PPP协议、HDLC协议)

今天又学会了一个知识&#xff0c;加油&#xff01; 目录 一、广域网 二、PPP协议 1、PPP协议应满足的要求 2、PPP协议无需满足的要求 3、PPP协议的三个组成部分 4、PPP协议的状态图 5、PPP协议的帧格式 三、HDLC协议 1、HDLC的站&#xff08;主站、从站、复合站&…

使用Kaptcha实现的验证码功能

目录 一.需求 二.验证码功能实现步骤 验证码 引入kaptcha依赖 完成application.yml配置文件 浏览器显示验证码 前端页面 登录页面 验证成功页面 后端 此验证码功能是以SpringBoot框架下基于kaptcha插件来实现的。 一.需求 1.页面生成验证码 2.输入验证码&#xff…

关于react native项目中使用react-native-wechat-lib@3.0.4

关于react native项目中使用react-native-wechat-lib3.0.4 插件官网安装依赖包&#xff08;Android和iOS下载插件完成后记得更新依赖&#xff0c;&#xff09;Android中配置1.在项目文件夹下面创建文件夹wxapi&#xff08;如上图&#xff09;2.在文件MainApplication.java中如下…

使用数组模拟栈的相关操作【栈1.1】

public class ArrayStackDemo {public static void main(String[] args) {ArrayStack arrayStack new ArrayStack(4);Scanner sc new Scanner(System.in);boolean loop true;char key ;while (loop) {System.out.println("栈操作菜单项");System.out.println(&q…

教育机构小程序管理系统的全方位优化

随着互联网的快速发展&#xff0c;线上教育也日益受到人们的关注和欢迎。为了满足广大学生和家长的需求&#xff0c;教育机构纷纷开发出自己的小程序管理系统。本文将详细介绍如何使用乔拓云平台&#xff0c;一键开发出自己的教育机构小程序管理系统。 1.进入乔拓云后台 首先&…

【SpringBoot】参数校验及异常处理

实现注册功能时经常遇到参数校验的问题。 参数校验 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId> </dependency>参数前添加注解&#xff0c;并指定校验规…

60V恒流IC SL8443B内置功率MOS 兼容PWM 降压LED恒流驱动芯片

以下是根据您的要求&#xff0c;按照知识经验类文章的特征所写的一篇关于“60V恒流IC 内置5V功率MOS 兼容PWM 降压LED恒流驱动芯片 SL8443B”的文章&#xff1a; 一、概述 SL8443B是一款60V恒流IC&#xff0c;内置5V功率MOS&#xff0c;兼容PWM降压LED恒流驱动芯片。它广泛应用…

Baumer工业相机堡盟工业相机如何通过BGAPISDK获取相机的各种信息如SN/ID等等(C++)

Baumer工业相机堡盟工业相机如何通过BGAPISDK获取相机的各种信息如SN/ID等等&#xff08;C&#xff09; Baumer工业相机Baumer工业相机通过SDK获取相关生产信息的技术背景通过SDK获取相机信息的代码分析获取Baumer工业相机相关信息Baumer工业相机相关参数信息获取的测试 Baumer…

neuq-acm预备队训练week 9 P8604 [蓝桥杯 2013 国 C] 危险系数

题目背景 抗日战争时期&#xff0c;冀中平原的地道战曾发挥重要作用。 题目限制 题目描述 地道的多个站点间有通道连接&#xff0c;形成了庞大的网络。但也有隐患&#xff0c;当敌人发现了某个站点后&#xff0c;其它站点间可能因此会失去联系。 我们来定义一个危险系数 DF…

【Linux】Linux运维基础

Linux简介&#xff1a; Linux是一个开源的操作系统内核&#xff0c;最初由Linus Torvalds创建。它通常与GNU工具一起使用&#xff0c;以创建一个完整的操作系统。Linux操作系统有许多基于内核的发行版&#xff0c;如Ubuntu、CentOS、Debian等&#xff0c;每个发行版都有其独特的…

KUKA机器人Loop循环的具体使用方法示例

KUKA机器人Loop循环的具体使用方法示例 如下图所示&#xff0c;新建一个示例程序&#xff0c; 如下图所示&#xff0c;添加一些动作指令&#xff0c; 如下图所示&#xff0c;如果想要机器人在第5行和第9行之间循环执行程序&#xff0c;则可以在第5行添加指令loop&#xff0…

Vue中插槽的使用

目录 一、默认插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 &#xff08;3&#xff09;后备内容 二、具名插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 三、作用域插槽 &#xff08;1&#xff09;概念 &#xff0…