JAVA技术栈,常见生产问题汇总

![](https://upload-images.jianshu.io/upload_images/28385926-1e89754e4bf29b23.png) >> IT行业中目前java技术栈仍然占据着主导的地位,在生产环境抢修中,还有一些非常常见的生产问题,依然是JVM相关的问题占比非常高,今年我们就来整理探讨一下这方面的问题 ## 常见的JVM问题类型 在参与多年的生产抢修过程中,下面列举的故障都是十分严重的,每一个故障都会导致生产系统不可用,给企业造成不可估量的损失,常见的故障问题主要是如下几种: - OOM内存溢出 - CPU资源开销非常高,超过90% - 线程死锁 - 线程等待 ## CPU资源开销高问题剖析 - OOM内存溢出在上篇文章中已经详细分享过,可以查看《JAVA内存溢出问题深入剖析》 - CPU资源开销高问题 这个问题很好理解,就是操作系统的CPU时间片资源都被java进程占用了,下面是一个代码示例,通过创建多个线程来抢占CPU时间片,来达到消耗光cpu资源的目的。(这里注意单个执行任务的线程无法做到),我们来看下相关的示例代码: ```java public class Highcpu { public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName()); try { // Thread.sleep(30 * 60 * 1000); int sum = 0; while (true) { // sum++; } } catch (Exception e) { e.printStackTrace(); } }); thread.setName("thread-" + i); thread.start(); } } } ``` 该代码运行后,cpu资源性能立马飙到了100%,只要线程一直运行,占用资源就一直不会被释放 ![](https://upload-images.jianshu.io/upload_images/28385926-93047dc5e15484b9.png) 现在我们来看一下,如何从运行中的线程信息中定位到问题代码 主要是如下几步: 1. 确认是java进程占用cpu高 2. 找到java进程下cpu占用高的线程,把十进制线程号转换成十六进制 3. 使用jstack工具打印出java线程堆栈信息,定位问题代码 - 下面是实操环节 1. 确认是java进程占用cpu高(我的环境是windows,linux系统使用的工具会有区别) ![](https://upload-images.jianshu.io/upload_images/28385926-aa378cf14cd15386.png) 2. 找到java进程下cpu占用高的线程,把十进制线程号转换成十六进制 这里我使用的是process explorer这个工具 ![](https://upload-images.jianshu.io/upload_images/28385926-e90351aee210423a.png) 这么多线程中找前两个线程进行分析,分别是20312和5900这两个线程,通过计算机转换成16进制数为:4F58和170C ![](https://upload-images.jianshu.io/upload_images/28385926-5c8ae2a3412bd2cb.png) 3. 使用jstack工具打印出java线程堆栈信息,定位问题代码 jstack 14432 > d:\heapdump\2023101601.log ![](https://upload-images.jianshu.io/upload_images/28385926-b595eb4477e716fa.png) 找到问题代码段,搞定! ## 线程死锁问题剖析 线程死锁问题简单解释如下:两个线程A,B A一直持有资源1,B一直持有资源2。这时A想要再持有资源1,B想要再持有资源2,一直获取不到,导致死锁。下面我们就用程序演示还原一下这个场景 ```java public class DeadLockDemo { public static void main(String[] args) { Resource resource1 = new Resource("资源1"); Resource resource2 = new Resource("资源2"); doSomething(resource1, resource2); doSomething(resource2, resource1); } private static void doSomething(Resource resource1, Resource resource2) { new Thread(() -> { // 获取资源1的锁 System.out.println(Thread.currentThread().getName() + " 请求" + resource1 + "的锁"); synchronized (resource1) { System.out.println(Thread.currentThread().getName() + " 获取到" + resource1 + "的锁"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取资源2的锁 System.out.println(Thread.currentThread().getName() + " 请求" + resource2 + "的锁"); synchronized (resource2) { System.out.println(Thread.currentThread().getName() + " 获取到" + resource2 + "的锁"); } } }).start(); } } class Resource { private String name; public Resource(String name) { this.name = name; } @Override public String toString() { return this.name; } } ``` 如上代码运行后,成功的实现了死锁的场景。两个线程都再等待对方资源导致无法继续执行后续任务。 ![](https://upload-images.jianshu.io/upload_images/28385926-2447ac449d56737b.png) 首先使用jps找到对应进程pid ![](https://upload-images.jianshu.io/upload_images/28385926-b54614b89e72c4f8.png) 再使用jstack 17908,找到了两个死锁的线程,成功定位到了问题代码段 ![](https://upload-images.jianshu.io/upload_images/28385926-097203e84abdc262.png) ![](https://upload-images.jianshu.io/upload_images/28385926-44874bf3f46d9c0a.png) ## 线程等待问题剖析 线程等待问题是当某一个线程一直持有锁不释放,导致其他线程无法获得该锁,一直处于等待状态。我们来看一下导致该问题的代码: ```java import java.util.LinkedList; import java.util.Queue; public class ObjectMethodTest { Queue queue = new LinkedList(); int MAX_SIZE = 1; // 假设队列长度只有1 , 只能存放一条数据 public void produce() { synchronized (queue) { // 队列满则等待队列空间 while (queue.size() == MAX_SIZE) { // 挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后获取队列里面的元素。 try { queue.wait(); System.out.println("-----等待消费----"); } catch (InterruptedException e) { e.printStackTrace(); } } // 生产元素, 并通知唤醒消费者 queue.add("hahaha"); } } public void consume() { synchronized (queue) { while (queue.size() == 0) { // 挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,然后获取队列里面的元素。 try { queue.wait(); System.out.println("-----等待生产-----"); } catch (InterruptedException e) { e.printStackTrace(); } } // 消费元素, 并通知唤醒生产者 System.out.println("消费成功:" + queue.poll()); queue.notifyAll(); } } public static void main(String[] args) { ObjectMethodTest objectMethodTest = new ObjectMethodTest(); // 10 个生产线程 for (int i = 0; i < 10; i++) { new Thread(objectMethodTest::produce).start(); } // 10 个消费线程 for (int i = 0; i < 10; i++) { new Thread(objectMethodTest::consume).start(); } } } ``` 代码实现逻辑说明: 定义了一个队列(Queue queue)和队列最大长度(int MAX_SIZE = 1)。 produce 方法用于生产元素。它通过synchronized (queue)来获取队列的锁,确保生产和消费操作的互斥执行。**如果队列已满,生产者会进入等待状态,并一直持有该队列锁。** consume 方法用于消费元素。同样,它也使用synchronized (queue)来获取队列的锁。如果队列为空,消费者会进入等待状态,释放队列的锁,以便生产者可以获取锁并生产元素。 在 main 方法中,创建了一个 ObjectMethodTest 对象,并启动了10个生产者线程和10个消费者线程。 **该场景会导致当持有该队列锁的线程,队列大小达到最大后。该线程会进入挂起状态,并一直持有该队列锁。导致其他线程无法获取,一直等待。** 我们来定位一下问题代码段,主要步骤和排查死锁问题一样 1. 通过jps找到进程pid ![](https://upload-images.jianshu.io/upload_images/28385926-530310310847a433.png) 2. 使用jstack pid 打印出线程堆栈 我们可以看到消费者线程都在等待 - waiting on <0x000000076c0bf8b8>这个锁资源,并且我们知道这个锁是一个java.util.LinkedList类型的资源锁。 并且定位到了问题代码段:at com.example.demo.controller.ObjectMethodTest.consume(ObjectMethodTest.java:38) ![](https://upload-images.jianshu.io/upload_images/28385926-614b144662d77878.png) 同时生产者线程也同样在等待这个锁资源 - waiting on <0x000000076c0bf8b8> (a java.util.LinkedList) 同时也指出了问题代码段 ![](https://upload-images.jianshu.io/upload_images/28385926-cc2165f1060af832.png) ## 总结 知其然,知其所以然。我们通过代码示例还原了如下几个常见的生产问题场景,并实操了如何进行问题排查,定位到问题代码 - CPU资源开销非常高,超过90% - 线程死锁 - 线程等待 希望上面的案例能够帮助到你,我会带来更多更干的干货分享,欢迎关注我! 本文由[mdnice](https://mdnice.com/?platform=6)多平台发布

你可能感兴趣的:(JAVA技术栈,常见生产问题汇总)