
IT行业中目前java技术栈仍然占据着主导的地位,在生产环境抢修中,还有一些非常常见的生产问题,依然是JVM相关的问题占比非常高,今年我们就来整理探讨一下这方面的问题
常见的JVM问题类型
在参与多年的生产抢修过程中,下面列举的故障都是十分严重的,每一个故障都会导致生产系统不可用,给企业造成不可估量的损失,常见的故障问题主要是如下几种:
-
OOM内存溢出 -
CPU资源开销非常高,超过90% -
线程死锁 -
线程等待
CPU资源开销高问题剖析
-
OOM内存溢出在上篇文章中已经详细分享过,可以查看《JAVA内存溢出问题深入剖析》
-
CPU资源开销高问题 这个问题很好理解,就是操作系统的CPU时间片资源都被java进程占用了,下面是一个代码示例,通过创建多个线程来抢占CPU时间片,来达到消耗光cpu资源的目的。(这里注意单个执行任务的线程无法做到),我们来看下相关的示例代码:
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%,只要线程一直运行,占用资源就一直不会被释放

现在我们来看一下,如何从运行中的线程信息中定位到问题代码
主要是如下几步:
-
确认是java进程占用cpu高 -
找到java进程下cpu占用高的线程,把十进制线程号转换成十六进制 -
使用jstack工具打印出java线程堆栈信息,定位问题代码
-
下面是实操环节
-
确认是java进程占用cpu高(我的环境是windows,linux系统使用的工具会有区别)

-
找到java进程下cpu占用高的线程,把十进制线程号转换成十六进制
这里我使用的是process explorer这个工具

这么多线程中找前两个线程进行分析,分别是20312和5900这两个线程,通过计算机转换成16进制数为:4F58和170C

-
使用jstack工具打印出java线程堆栈信息,定位问题代码 jstack 14432 > d:heapdump2023101601.log

找到问题代码段,搞定!
线程死锁问题剖析
线程死锁问题简单解释如下:两个线程A,B A一直持有资源1,B一直持有资源2。这时A想要再持有资源1,B想要再持有资源2,一直获取不到,导致死锁。下面我们就用程序演示还原一下这个场景
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;
}
}
如上代码运行后,成功的实现了死锁的场景。两个线程都再等待对方资源导致无法继续执行后续任务。

首先使用jps找到对应进程pid

再使用jstack 17908,找到了两个死锁的线程,成功定位到了问题代码段

线程等待问题剖析
线程等待问题是当某一个线程一直持有锁不释放,导致其他线程无法获得该锁,一直处于等待状态。我们来看一下导致该问题的代码:
import java.util.LinkedList;
import java.util.Queue;
public class ObjectMethodTest {
Queue<String> queue = new LinkedList<String>();
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
produce 方法用于生产元素。它通过synchronized (queue)来获取队列的锁,确保生产和消费操作的互斥执行。如果队列已满,生产者会进入等待状态,并一直持有该队列锁。
consume 方法用于消费元素。同样,它也使用synchronized (queue)来获取队列的锁。如果队列为空,消费者会进入等待状态,释放队列的锁,以便生产者可以获取锁并生产元素。
在 main 方法中,创建了一个 ObjectMethodTest 对象,并启动了10个生产者线程和10个消费者线程。
该场景会导致当持有该队列锁的线程,队列大小达到最大后。该线程会进入挂起状态,并一直持有该队列锁。导致其他线程无法获取,一直等待。
我们来定位一下问题代码段,主要步骤和排查死锁问题一样
我们可以看到消费者线程都在等待 – waiting on <0x000000076c0bf8b8>这个锁资源,并且我们知道这个锁是一个java.util.LinkedList类型的资源锁。
并且定位到了问题代码段:at com.example.demo.controller.ObjectMethodTest.consume(ObjectMethodTest.java:38)
同时生产者线程也同样在等待这个锁资源 – waiting on <0x000000076c0bf8b8> (a java.util.LinkedList) 同时也指出了问题代码段

总结
知其然,知其所以然。我们通过代码示例还原了如下几个常见的生产问题场景,并实操了如何进行问题排查,定位到问题代码
-
CPU资源开销非常高,超过90% -
线程死锁 -
线程等待
希望上面的案例能够帮助到你,我会带来更多更干的干货分享,欢迎关注我!
原文始发于微信公众号(云计算解决方案架构师):JAVA技术栈,常见生产问题汇总
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/225706.html