阿古达芝麻开门 - 博客园

如何定位并解决生产环境偶发的死锁或高线程等待问题

先监控与告警:
暴露线程池/线程数、队列长度、请求并发、响应时间、GC/CPU 等指标(Micrometer/Prometheus/Grafana)。
设置阈值告警(线程数、队列溢出、长时间 blocking)。
采集运行时诊断信息(尽量在线收集,避免重启):
常用命令:jstack -l <pid> > thread-dump.txt、jcmd <pid> Thread.print、Linux: kill -3 <pid>。容器:kubectl exec 到容器里执行。
使用 Java Flight Recorder(JFR)记录锁等待/阻塞(Monitor Wait/Block)并用 Mission Control 分析:jcmd <pid> JFR.start name=rec settings=profile filename=rec.jfr,运行后 JFR.stop。
使用 Arthas(thread、ttop)、async-profiler 或 VisualVM / Mission Control 分析热点和锁竞争。
快速分析要点:
在堆栈中查找 BLOCKED(等待锁)和 WAITING/ TIMED_WAITING 的线程。注意哪个线程持有锁(owner)与等待链(look for cycles)。
判断是“死锁”(互相持有对方锁形成环)还是“线程池耗尽/队列积压/慢阻塞调用”。
检查是否大量同步块/长时间持有锁、阻塞 IO、外部调用(DB、RPC)或定时等待导致线程阻塞。
常见修复策略:
缩小锁粒度 / 减少锁持有时间;使用无锁并发结构(ConcurrentHashMap、LongAdder、Atomic*)。
如果必须加锁,显式使用 ReentrantLock + tryLock(timeout) 或增加超时和报警;保持一致的锁获取顺序以避免循环依赖。
对耗时 IO 使用异步/线程池隔离(bulkhead),为外部调用设置超时与熔断(resilience4j)。
对线程池做合理容量与拒绝策略(bounded queue + rejection handler),监控并扩容或降级。
在关键路径加埋点日志(进入/退出锁、外部调用开始/结束)以便事后追踪。
生产缓解与复现:
若不可立即修复,可临时在生产开启更多诊断(JFR、更多采样频率)、增加线程池或限流,避免连环失效。
在测试环境复现后用更严格的测试(负载+故障注入)验证修复。
事后策略:
将线程转储、JFR、异常堆栈与请求 id 持久化以便事后关联分析。
编写自动检测脚本与告警(发现死锁或长时间 BLOCKED 自动上报)。

 

运行时周期性检测死锁并把线程堆栈写到日志文件(可部署到线上作为守护进程,低开销)。简单说明:该类每隔 N 秒检查 JVM 是否存在死锁(使用 ThreadMXBean.findDeadlockedThreads),若有则把相关线程堆栈写入文件;同时周期性导出所有线程栈供离线分析

// java
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class DeadlockWatcher implements Runnable {
    private final ThreadMXBean tm = ManagementFactory.getThreadMXBean();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
        Thread t = new Thread(r, "deadlock-watcher");
        t.setDaemon(true);
        return t;
    });
    private final String outFile;
    private final int periodSeconds;

    public DeadlockWatcher(String outFile, int periodSeconds) {
        this.outFile = outFile;
        this.periodSeconds = periodSeconds;
    }

    public void start() {
        scheduler.scheduleAtFixedRate(this, periodSeconds, periodSeconds, TimeUnit.SECONDS);
    }

    public void stop() {
        scheduler.shutdownNow();
    }

    @Override
    public void run() {
        try (PrintWriter pw = new PrintWriter(new FileWriter(outFile, true))) {
            pw.println("==== Thread Snapshot at " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + " ====");
            // 检查死锁(包括 ownable synchronizers)
            long[] deadlocked = tm.findDeadlockedThreads(); // null if none
            if (deadlocked != null && deadlocked.length > 0) {
                pw.println("DEADLOCK detected! Thread ids: " + Arrays.toString(deadlocked));
                ThreadInfo[] tis = tm.getThreadInfo(deadlocked, true, true);
                for (ThreadInfo ti : tis) {
                    pw.println(threadInfoToString(ti));
                }
            } else {
                // 也记录所有线程栈用于分析高等待
                ThreadInfo[] all = tm.dumpAllThreads(true, true);
                for (ThreadInfo ti : all) {
                    pw.println(threadInfoToString(ti));
                }
            }
            pw.flush();
        } catch (IOException e) {
            // 如果写文件失败,打印到 stderr(尽量不抛异常中断调度)
            e.printStackTrace();
        }
    }

    private String threadInfoToString(ThreadInfo ti) {
        StringBuilder sb = new StringBuilder();
        sb.append('"').append(ti.getThreadName()).append("\" Id=").append(ti.getThreadId())
          .append(" State=").append(ti.getThreadState()).append('\n');
        if (ti.getLockName() != null) {
            sb.append("  waiting on lock: ").append(ti.getLockName()).append('\n');
        }
        if (ti.getLockOwnerName() != null) {
            sb.append("  lock owner: ").append(ti.getLockOwnerName()).append(" Id=").append(ti.getLockOwnerId()).append('\n');
        }
        for (StackTraceElement ste : ti.getStackTrace()) {
            sb.append("    at ").append(ste.toString()).append('\n');
        }
        if (ti.getLockedMonitors() != null && ti.getLockedMonitors().length > 0) {
            sb.append("  Locked monitors:\n");
            Arrays.stream(ti.getLockedMonitors()).forEach(m -> sb.append("    - ").append(m).append('\n'));
        }
        if (ti.getLockedSynchronizers() != null && ti.getLockedSynchronizers().length > 0) {
            sb.append("  Locked synchronizers:\n");
            Arrays.stream(ti.getLockedSynchronizers()).forEach(s -> sb.append("    - ").append(s).append('\n'));
        }
        return sb.toString();
    }

    // 运行示例:在应用启动处调用 new DeadlockWatcher("`/var/log/app/thread-dump.log`", 30).start();
    public static void main(String[] args) {
        String out = args.length > 0 ? args[0] : "thread-dump.log";
        int period = args.length > 1 ? Integer.parseInt(args[1]) : 30;
        DeadlockWatcher w = new DeadlockWatcher(out, period);
        w.start();
        // keep JVM alive for demo
        try { Thread.sleep(TimeUnit.MINUTES.toMillis(30)); } catch (InterruptedException ignored) {}
        w.stop();
    }
}

 

posted @ 2025-11-04 22:35  阿古达芝麻开门  阅读(2)  评论(0)    收藏  举报