java线程卡住排查_基于 Java 线程栈 排查问题
除日志外,还有没有别的方式跟踪线上服务问题呢?或者,跟踪并排除日志里无法发现的问题?
方法当然是有的,就是通过现场快照定位并发现问题。我们所说的现场,主要指这两方面:
Java 线程栈。线程栈是Java线程工作的快照,可以获得当前线程在做什么;
Java 内存堆。堆是JVM的内存快照,可以获取内存分配相关信息。
今天,我们从 Java 线程角度,研究下基于线程栈如果排除问题。
1. Java 线程状态变换
在正式介绍线程栈之前,有必要先了解下 Java 线程的相关状态。
Java 线程就是在这几个状态间完成自己的整个生命周期。
状态
是够消耗 CPU
描述RUNNABLE
不确定
运行中 或者 就绪
WAITING
不消耗
1. object monitor 2. unpark
TIME_WAITING
不消耗
1. object monitor 2. unpark 3. sleep
BLOCKED
不消耗
object monitor
2. Java 线程栈
线程栈是问题的第一现场,从线程栈中可以获得很多日志以外的瞬时信息。
2.1 获取栈
jstack pid
kill -3 pid
强烈建议使用 jstack 命令!一者,方便重定向;二者,最大限度的避免 kill 这种高危命令的使用。
3.2 栈信息
核心信息:
线程名。务必给线程起一个优雅的名字;
Java 线程 id。全局唯一,16进制显示;
Native 线程 id。OS 线程id,16进制,与系统资源对应起来;
状态。线程所处状态,最关心 RUNNABLE 状态,实实在在消耗 CPU;
锁信息。获取锁、等待锁;
调用栈信息。方法调用链,类、方法、代码行号,问题排查关键;
3.3 线程栈视角
从线程中获取信息,有两个视角。
单次线程栈
总线程数量
是否发生死锁
线程所处状态
线程调用栈
多次线程栈
是否一直执行同一段代码
是否一直等待同一个锁
一般会导出多份线程栈,共 10 份,每个 2s 打一份。
3. 问题排查
线程栈不同于日志,是程序运行时的快照,可以定位很多诡异问题。
3.1 死锁
死锁是程序最为严重的问题,导致进程 hold 在那,无法处理正常请求。
死锁发生存在几个条件:
存在互斥条件;
保持并请求资源;
不能剥夺资源;
出现环路等待
3.1.1 Object 死锁
主要指使用 synchronized 关键字,通过对象锁保护资源,导致的死锁。
测试代码如下:
private final Object objectA = new Object();
private final Object objectB = new Object();
private String objectMethod1(){
synchronized (this.objectA){
sleepForMs(10);
synchronized (this.objectB){
return getCurrentTime();
}
}
}
private String objectMethod2(){
synchronized (this.objectB){
sleepForMs(10);
synchronized (this.objectA){
return getCurrentTime();
}
}
}
private ExecutorService deadlockExecutor = Executors.newFixedThreadPool(20, new BasicThreadFactory
.Builder()
.namingPattern("DeadLock-Thread-%d")
.build()
);
@RequestMapping("object")
public DeferredResult object(){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture r1 = CompletableFuture.supplyAsync(this::objectMethod1, this.deadlockExecutor);
CompletableFuture r2 = CompletableFuture.supplyAsync(this::objectMethod2, this.deadlockExecutor);
CompletableFuture.allOf(r1, r2).thenRun( ()-> result.setResult("SUCCESS"));
return result;
}
请求 /deadlock/object 返回超时,打印 Java 栈信息,发生死锁:
从栈信息中,我们可以获得以下信息:
发生死锁现象
发生死锁的相关线程
线程获取哪个锁,又再等待什么锁
3.1.2 Lock 死锁
主要指使用 Lock 对象进行资源保护,从而导致的死锁。
测试代码如下:
private final Lock lockA = new ReentrantLock();
private final Lock lockB = new ReentrantLock();
private String lockMethod1(){
try {
this.lockA.lock();
sleepForMs(10);
try {
this.lockB.lock();
return getCurrentTime();
}finally {
this.lockB.unlock();;
}
}finally {
this.lockA.unlock();
}
}
private String lockMethod2(){
try {
this.lockB.lock();
sleepForMs(10);
try {
this.lockA.lock();
return getCurrentTime();
}finally {
this.lockA.unlock();;
}
}finally {
this.lockB.unlock();
}
}
@RequestMapping("lock")
public DeferredResult lock(){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture r1 = CompletableFuture.supplyAsync(this::lockMethod1, this.deadlockExecutor);
CompletableFuture r2 = CompletableFuture.supplyAsync(this::lockMethod2, this.deadlockExecutor);
CompletableFuture.allOf(r1, r2).thenRun( ()-> result.setResult("SUCCESS"));
return result;
}
请求 /deadlock/lock 返回超时,打印 Java 栈信息,发生死锁:
和上个栈信息非常相似,发生了死锁现象。但,丢失了很重要的一个信息“线程获得哪个锁,又在申请哪个锁”,这可能就是 JVM 内置锁和 AQS 家族的区别。
3.2 线程数过高
线程数过高,主要由于线程池的不合理使用,比如没有设置最大线程数。
测试代码:
@RestController
@RequestMapping("many-thread")
public class ManyThreadController {
private ExecutorService executorService = Executors.newCachedThreadPool(new BasicThreadFactory
.Builder()
.namingPattern("Many-Thread-%d")
.build()
);
@RequestMapping("/{tasks}")
public DeferredResult manyThreads(@PathVariable int tasks){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture[] futures = new CompletableFuture[tasks];
for (int i=0;i
futures[i] = CompletableFuture.supplyAsync(this::getValue, executorService);
}
CompletableFuture.allOf(futures).thenRun( ()-> result.setResult("SUCCESS"));
return result;
}
private String getValue() {
sleepForMs(50);
return getCurrentTime();
}
}
请求 /many-thread/2000 ,查看栈信息:
存在 1729 个相似线程,如果在次加大 loop ,还可能会出现异常信息,有兴趣可以自行测试。
3.3 CPU 过高
一般是大集合处理或死循环导致。
测试代码如下:
@RestController
@RequestMapping("high-cpu")
public class HighCPUController {
private ExecutorService executorService = Executors.newFixedThreadPool(1, new BasicThreadFactory
.Builder()
.namingPattern("High-CPU-%d")
.build()
);
@RequestMapping("/{loop}")
public DeferredResult highCpu(@PathVariable long loop){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture.supplyAsync(()->{
for (int i=0;i
try {
Math.cos(i + 10);
}catch (Exception e){
}
}
return getCurrentTime();
}, executorService).thenAccept(r->result.setResult(r));
return result;
}
}
请求 /high-cpu/100000000000, CPU 会飙升。
3.3.1 多次线程栈对比
多次获取线程栈,特定线程长期停留在一个运行代码。
3.3.2 线程跟踪
先得到高 CPU 线程,在通过 nid 与线程栈线程对应,从而定位问题线程。
top -Hp pid。获取高 CPU 的线程号;
将线程号转换为 16 进制;
在线程栈中通过 nid 查找对应的线程;
3.4 资源不足
各种 pool 最常见问题。
Pool 工作原理:
测试代码如下:
@RestController
@RequestMapping("low-resource")
public class LowResourceController {
private ExecutorService executorService = Executors.newCachedThreadPool(new BasicThreadFactory
.Builder()
.namingPattern("Low-Resource-%d")
.build()
);
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("/{batch}")
public DeferredResult lowReource(@PathVariable int batch){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture[] futures = new CompletableFuture[batch];
for (int i=0;i
futures[i] = CompletableFuture.supplyAsync(this::getValue, executorService);
}
CompletableFuture.allOf(futures).thenRun( ()-> result.setResult("SUCCESS"));
return result;
}
private String getValue() {
try {
return redisTemplate.execute((RedisCallback)(redisConnection -> {
sleepForMs(5000);
return getCurrentTime() + redisConnection;
}));
}catch (Exception e){
e.printStackTrace();
}
return "ERROR";
}
}
请求 /low-resource/1000 超时后,查看堆栈信息:
可见,存在 998 个线程在等待 Jedis 资源。
3.5 锁级联
线程可以形成自己的依赖链条,增加问题排查的难度。
3.5.1 Future 级联
代码如下:
@RequestMapping("future")
public DeferredResult future(){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
Future future = this.executorService.submit(()->{
sleepForMs(5000);
return getCurrentTime();
});
CompletableFuture[] futures = new CompletableFuture[CONCURRENT_COUNT];
for (int i=0;i
futures[i] = CompletableFuture.supplyAsync(()->{
try {
return future.get();
}catch (Exception e){
}
return "ERROR";
}, executorService);
}
CompletableFuture.allOf(futures).thenRun(()->result.setResult("SUCCESS"));
return result;
}
访问 /wait-chain/future 后,查看线程栈信息:
共有 100 个线程在 future.get 处进行等待。
3.5.2 Guave Cache 级联
Guava Cache 是最常用的 Local Cache,其内部做了并发处理,让多个线程请求同一个 Key,会发生什么事情呢?
测试代码如下:
private final LoadingCache cache;
public WaitChainController(){
cache = CacheBuilder.newBuilder()
.build(new CacheLoader() {
@Override
public String load(String s) throws Exception {
sleepForMs(5000);
return getCurrentTime();
}
});
}
@RequestMapping("guava-cache")
public DeferredResult guavaCache(){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture[] futures = new CompletableFuture[CONCURRENT_COUNT];
for (int i=0;i
futures[i] = CompletableFuture.supplyAsync(this::loadFromGuava, executorService);
}
CompletableFuture.allOf(futures).thenRun(()->result.setResult("SUCCESS"));
return result;
}
访问 /wait-chain/guava-cache 后,查看线程栈信息:
可见有 98 个线程在 Sync.get 处等待,整个现象和 Future 非常相似。
3.5.3 Logger 级联
日志是最常用的组件,也是最容易忽略的组件,如果多个线程同时访问日志的写操作,会产生什么精致的?
测试代码如下:
@RequestMapping("logger")
public DeferredResult logger(){
DeferredResult result = new DeferredResult<>(10 * 1000L, "TimeOut");
CompletableFuture[] futures = new CompletableFuture[CONCURRENT_COUNT];
for (int i=0;i
futures[i] = CompletableFuture.supplyAsync(this::writeLogger, executorService);
}
CompletableFuture.allOf(futures).thenRun(()->result.setResult("SUCCESS"));
return result;
}
private String writeLogger(){
for (int i = 0;i<10000;i++){
LOGGER.info("{}", i);
}
return getCurrentTime();
}
访问 /wait-chain/logger 后,查看线程栈信息.
写堆栈:
从日志中可见,Wait-Chain-Thread-52 线程正在执行文件写操作。
等待栈:
而有 98 个线程处于等待锁的状态。
4. 小结
Java 线程栈是线程运行时快照,可以帮助我们定位很多问题。掌握这一技能会让我们在日常工作中得心应手。
最后附上 项目源码
java线程卡住排查_基于 Java 线程栈 排查问题相关推荐
- java项目----教务管理系统_基于Java的教务管理系统
java项目----教务管理系统_基于Java的教务管理系统 2022-04-22 18:18·java基础 最近为客户开发了一套学校用教务管理系统,主要实现学生.课程.老师.选课等相关的信息化管理功 ...
- java网上书店系统_基于JAVA/JSP的网上书店系统
第一章 JAVA的网络功能与编程 1-1 JAVA语言简介 Java是一种简单易用.完全面向对象.具有平台无关性且安全可靠的主要面向Internet的开发工具.自从1995年正式问世以来,Java的快 ...
- java中变量 关键字_基于java的voliate关键字详解
voliate关键字的作用: 一.内存可见性 基于缓存一致性协议,当用voliate关键字修饰的变量改动时,cpu会通知其他线程,缓存已被修改,需要更新缓存.这样每个线程都能获取到最新的变量值. 二. ...
- java农产品查询系统_基于java的农产品销售系统的设计与实现论文.docx
基于java的农产品销售系统的设计与实现论文.docx 分类号_______________ 密级________________ UDC _______________ 学号_毕业设计(论文)论文题 ...
- java 计算移动平均线_基于Java语言开发的个性化股票分析技术:移动平均线(MA)...
基于Java语言开发的个性化股票分析技术:移动平均线(MA) 基于 Java 语言开发的个性化股票分析技术:移动平均线(MA)移动平均线(MA)是以道·琼斯的"平均成本概念"为理论 ...
- 大学生java项目创意申请表_基于java ee的大学生作品展示系统.pdf
基于java ee的大学生作品展示系统.pdf 还剩 4页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,喜欢就下载吧,价低环保! 内容要点: 第34卷第3期 2015年6月 南昌工程学院学 ...
- java 静态对象赋值_基于Java class对象说明、Java 静态变量声明和赋值说明(详解)...
先看下JDK中的说明: java.lang.Object java.lang.Class Instances of the class Class represent classes and inte ...
- java课程设计拼图_基于Java拼图游戏的设计与实现(含录像)
基于拼图游戏的设计与实现(含录像) 摘 要 本拼图游戏是基于J2SE平台开发的,它是一个Application,它的游戏规则和诺亚舟里的拼图游戏是一样的.这个游戏将一张大图切割成N张小图,然后在 ...
- 基于java的信访项目_基于JAVA的某省人大信访信息系统的设计与实现
分 类 号 : TP 3 1 1 . 5 单 位 代码 : 1 01 8 3 研 究 生 学 号 : 2 0 1 35 3 H3 08 密 级 : 公 开 吉 林 大 学 硕 士 学 位 ...
最新文章
- linux系统 长久记录所有用户所有操作记录
- JSP和Servlet的六种中文乱码处理方法
- 输入流与输出流的区别
- Anaconda是什么?Anconda下载安装教程 - Python零基础入门教程
- 图之遍历--广度优先遍历
- PAT 1073 多选题常见计分法(20)(代码+思路)
- 影响 Chrome、Edge 等浏览器的 V8 引擎0day
- ORA-22859 无效的列修改
- iOS开发那些-如何打包iOS应用程序
- php中的date()函数d y m l等字母的表示方法
- 5、手把手教React Native实战之盒子模型BoxApp
- 康佳电视android,康佳电视怎么连接手机 康佳电视连接手机步骤【图文介绍】
- 24个希腊字母及其读音表
- 【STM32技巧】HX711称重芯片详细说明
- 易语言调用c 文本乱码,解决易语言编程乱码的问题
- 成长,进一寸有一寸的欢喜
- Skia引擎API整理介绍(skia in Android 2.3 trunk)
- 阿里DruidDataSource访问RDS安全级别受限问题排查
- java手动注册filter,SpringBoot注册Filter的两种实现方式
- 软件工程《构建之法》—概论
热门文章
- C语言填空题10道,3道C语言填空题,谁帮忙做下,谢谢
- tina中信号带宽_如何评测示波器带宽
- 二级计算机vf里的sql,计算机等级考试二级VF考点:SQL语言
- Win11系统点击回滚没有反应是怎么回事
- windows7怎么清空电脑只剩系统
- windows7系统电脑管理员权限怎么更改
- null、undefined、NaN区分解析和条件判定,以及在IF条件中的判定
- 轮询、长轮询与Web Socket的前端实现
- @RequestBody 和 @RequestParam可以同时使用
- pandas series取值_【小学生级】pandas入门到精通备查表——AI未来系列3