java线程卡住排查_基于 Java 线程栈 排查问题

除日志外,还有没有别的方式跟踪线上服务问题呢?或者,跟踪并排除日志里无法发现的问题?

方法当然是有的,就是通过现场快照定位并发现问题。我们所说的现场,主要指这两方面:

Java 线程栈。线程栈是Java线程工作的快照,可以获得当前线程在做什么;

Java 内存堆。堆是JVM的内存快照,可以获取内存分配相关信息。

今天,我们从 Java 线程角度,研究下基于线程栈如果排除问题。

1. Java 线程状态变换

在正式介绍线程栈之前,有必要先了解下 Java 线程的相关状态。

AAffA0nNPuCLAAAAAElFTkSuQmCC

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 栈信息

AAffA0nNPuCLAAAAAElFTkSuQmCC

核心信息:

线程名。务必给线程起一个优雅的名字;

Java 线程 id。全局唯一,16进制显示;

Native 线程 id。OS 线程id,16进制,与系统资源对应起来;

状态。线程所处状态,最关心 RUNNABLE 状态,实实在在消耗 CPU;

锁信息。获取锁、等待锁;

调用栈信息。方法调用链,类、方法、代码行号,问题排查关键;

3.3 线程栈视角

从线程中获取信息,有两个视角。

单次线程栈

总线程数量

是否发生死锁

线程所处状态

线程调用栈

多次线程栈

是否一直执行同一段代码

是否一直等待同一个锁

一般会导出多份线程栈,共 10 份,每个 2s 打一份。

3. 问题排查

线程栈不同于日志,是程序运行时的快照,可以定位很多诡异问题。

3.1 死锁

死锁是程序最为严重的问题,导致进程 hold 在那,无法处理正常请求。

AAffA0nNPuCLAAAAAElFTkSuQmCC

死锁发生存在几个条件:

存在互斥条件;

保持并请求资源;

不能剥夺资源;

出现环路等待

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 栈信息,发生死锁:

AAffA0nNPuCLAAAAAElFTkSuQmCC

从栈信息中,我们可以获得以下信息:

发生死锁现象

发生死锁的相关线程

线程获取哪个锁,又再等待什么锁

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 栈信息,发生死锁:

AAffA0nNPuCLAAAAAElFTkSuQmCC

和上个栈信息非常相似,发生了死锁现象。但,丢失了很重要的一个信息“线程获得哪个锁,又在申请哪个锁”,这可能就是 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 ,查看栈信息:

AAffA0nNPuCLAAAAAElFTkSuQmCC

存在 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 多次线程栈对比

多次获取线程栈,特定线程长期停留在一个运行代码。

AAffA0nNPuCLAAAAAElFTkSuQmCC

3.3.2 线程跟踪

先得到高 CPU 线程,在通过 nid 与线程栈线程对应,从而定位问题线程。

top -Hp pid。获取高 CPU 的线程号;

将线程号转换为 16 进制;

在线程栈中通过 nid 查找对应的线程;

3.4 资源不足

各种 pool 最常见问题。

Pool 工作原理:

AAffA0nNPuCLAAAAAElFTkSuQmCC

测试代码如下:

@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 超时后,查看堆栈信息:

AAffA0nNPuCLAAAAAElFTkSuQmCC

可见,存在 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 后,查看线程栈信息:

AAffA0nNPuCLAAAAAElFTkSuQmCC

共有 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 后,查看线程栈信息:

AAffA0nNPuCLAAAAAElFTkSuQmCC

可见有 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 后,查看线程栈信息.

写堆栈:

AAffA0nNPuCLAAAAAElFTkSuQmCC

从日志中可见,Wait-Chain-Thread-52 线程正在执行文件写操作。

等待栈:

AAffA0nNPuCLAAAAAElFTkSuQmCC

而有 98 个线程处于等待锁的状态。

4. 小结

Java 线程栈是线程运行时快照,可以帮助我们定位很多问题。掌握这一技能会让我们在日常工作中得心应手。

最后附上 项目源码

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java实验二答案天津商业大学_天津商业大学信息安全实验一

天津商业大学信息安全实验一 1《信息安全技术》实 验 报 告 书实验名称&#xff1a; 实验一Internet应用风险专 业&#xff1a; 电子商务班 级&#xff1a; 1203班姓 名&#xff1a; 代常发学 号&#xff1a; 20124934指导老师&#xff1a; 丁雷信息工程学院2015 年 5 月2目 录…

java前端的日期插件_几个前端时间插件总结

几个前端时间插件总结总结一下几款时间插件&#xff0c;分别是- [ ] jeDate 手册http://www.jemui.com/jedate/- [ ] bootstrap-datetimepicker 下载地址- [ ] My97DatePicker 下载地址- [ ] jQuery UI 插件Datepicker下载地址并没有哪款完全超越另外一款&#xff0c;主要还是看…

Java插件自动保存浏览器书签_多浏览器书签同步插件EverSync

有时上网时会遇到浏览器不能正常显示的问题。(比如我的火狐浏览器无法正确显示微信公众号管理后台&#xff0c;在chrome上可以正常显示)&#xff0c;所以我的电脑里安装了chrome和firefox两个浏览器。但是时间长了&#xff0c;会出现两个浏览器上书签不同步的问题。原来自己的解…

php oracle 中文字段,怎么解决php oracle乱码问题

php oracle乱码是由于没有正确的配置字符集信息导致的&#xff0c;其解决办法就是通过PLSQL运行“select * from V$NLS_PARAMETERS;”获取oracle的字符集&#xff0c;并重新设置正确的字符集即可。PHP Oracle 中文乱码问题通常缺省配置连接Oracle在处理中文时都会遇到乱码问题&…

用matlab数学综合实验,MATLAB与数学实验(第2版)

MATLAB与数学实验(第2版)作者&#xff1a;艾冬梅 李艳晴 张丽静 刘琳出版日期&#xff1a;2014年06月文件大小&#xff1a;46.48M支持设备&#xff1a;&#xffe5;18.00在线试读适用客户端&#xff1a;言商书局iPad/iPhone客户端&#xff1a;下载 Android客户端&#xff1a…

matlab破损皮革定位,matlab-code-of-TDOAFDOa 干扰源定位代码,应该在 的求解过程中有帮助。 276万源代码下载- www.pudn.com...

文件名称: matlab-code-of-TDOAFDOa下载 收藏√ [5 4 3 2 1 ]开发工具: matlab文件大小: 38 KB上传时间: 2014-05-31下载次数: 25提 供 者: qqq详细说明&#xff1a;干扰源定位代码,应该在干扰源定位的求解过程中有帮助。-code for tdoa and fdoa文件列表(点击判断是否您…

debian 安装php gd2,如何在Debian Linux中为PHP安装Ioncube

在Debian Linux系统中安装PHP Ioncube加载器。 Ioncube用作PHP应用程序的加密和解密实用程序&#xff0c;通过它我们可以保护数据安全。 它还可以限制PHP应用程序执行未授权。 它还有助于加速提供的页面。 IonCube加载器(Ioncube Loaders)用于在Web服务器上运行时解码编码文件。…

php如何输出关联数组的值,php - 如何从PHP关联数组中获取确切的输出 - SO中文参考 - www.soinside.com...

我试图在关联数组上使用foreach循环创建一个html表。这是关于数组的更多细节。$assoc_array array("0" > array("project_id" > "1","emp_id" > "123","emp_name" > "Max","project&…

linux文件属性是什么意思,Linux文件属性

Linux是一种多用户系统&#xff0c;不同的用户处于不同的地位&#xff0c;拥有不同的权限。为了保护系统的安全性&#xff0c;Linux对不同用户访问同一文件的权限做了规定。我们可以使用ls -l命令来显示一个文件的信息&#xff1a;37944FD1-FBEF-4EDC-80BA-B5276F4242A9.png我们…

linux文件赋予755权限,Linux文件和目录的777、755、644权限解释

Linux文件和目录的权限1.文件权限在linux系统中,文件或目录的权限可以分为3种:r:4 读w:2 写x:1 执行(运行)&#xff0d;&#xff1a;对应数值0数字 4 、2 和 1表示读、写、执行权限rwx 4 2 1 7 (可读写运行)rw 4 2 6 (可读写不可运行)rx 4 1 5 (可读可运行不可写)示例…

linux下删除已经创建的数据库,MongoDB 数据库的创建和删除

MongoDB 创建数据库语法MongoDB 创建数据库的语法格式如下&#xff1a;useDATABASE_NAME如果数据库不存在&#xff0c;则创建数据库&#xff0c;否则切换到指定数据库。实例以下实例我们创建了数据库 runoob:>userunoobswitched to db runoob>dbrunoob>如果你想查看所…

支持1050ti显卡的linux系统,NVIDIA 的 GTX1050 Ti 与 GTX1050 显卡登场

一年来都忙着更新全系列显卡家族的 NVIDIA&#xff0c;先从 GTX1080 和 GTX1070 开始&#xff0c;到 GTX1060 和卡王 Titan X&#xff0c;一步一步将整条产品线升级到 Pascal 核心。今天的 GTX1050 和 GTX1050 Ti 则是补上了中低端市场的短板&#xff0c;让 AMD 享受了两个月优…

c语言项开发班级登入系统,c语言--班级管理系统

满意答案dgfetc5832013.12.10采纳率&#xff1a;47% 等级&#xff1a;12已帮助&#xff1a;14710人class student{public:char m_strName[10]; // 姓名int m_nNum; // 学号float m_dScore[4]; // 成绩};void InputScore(student* p, int nNumber){if(nNumber > 10){print…

android 高度百分比,如何在Android中进行百分比高度和宽度?

现在&#xff0c;可以用Guidelines定位百分比值xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto"android:layout_width"match_parent"android:layout_height"match_parent&q…

android fragment 弹出对话框,Android中使用Dialogfragment显示对话框

其他注意事项&#xff1a;1、如何设置自己的Dialogfragment没有标题栏&#xff1f;可以通过两种方法来设置 &#xff0c;一种是使用dialogfragment的setStyle函数&#xff0c;另外就是使用getDialog().getWindow().requestFeature方法&#xff0c;具体代码如下Overridepublic D…

Android7.0 emui主题,全新EMUI5.0基于Android7.0 天生快,一生快!

EMUI5.0是基于Android 7.0开发的全新一代操作系统。 循着神秘古老的爱琴海带来的灵感&#xff0c;EMUI5.0用户界面的设计极其简单干净&#xff0c;令人赏心悦目。 因了解用户习惯而全新改善&#xff0c;EMUI5.0流畅自然的表现不会随时间递减&#xff0c;让生活得心应手&#xf…

在微信公众号中写html代码吗,微信公众号代码编写怎么做

微信公众号编写微信代码&#xff0c;因为有这方面的需要&#xff0c;需要去进行微信公众号代码编写。以下是学习啦小编为您带来的关于微信公众号代码编写&#xff0c;希望对您有所帮助。微信公众号代码编写微信公众平台编辑器不能直接编写微信代码&#xff0c;但是可以通过第三…

html代码在线分析,网站html代码解析

1、什么是HTML文件&#xff1f;HTML中文叫做“超文本标记语言”&#xff0c;一个HTML文件不仅包含文本内容&#xff0c;还包含一些标记&#xff0c;一个HTML文件的后缀名是.htm或者是.html。用文本编辑器(Dreamweaver)就可以编写HTML文件。2、html文件的基本结构&#xff1a;(成…

html 下拉到一定位置,浏览器向下滚动到一定位置继续滚动时,侧边导航固定在页面顶部,再滚动到一定位置时页面再向下滚动侧边导航不再固定。这种效果怎么实现呢...

1.浏览器向下滚动到一定位置继续滚动时&#xff0c;侧边导航固定在页面顶部&#xff0c;再滚动到一定位置时页面再向下滚动侧边导航不再固定。页面向上滚动到一定位置继续滚动时&#xff0c;侧边导航保持在原来位置。这种效果怎么实现呢2.、参考代码&#xff1a;$(function(){/…

计算机专业的英语文献,计算机专业英语论文参考文献

bentuoguai高分答主08-08TA获得超过1351个赞关于计算机信息管理系统&#xff0c;可以参考了&#xff1a;)~~Enterprise computer network management information system(MIS) is gradually use, it is the stage sign of our country of production power development, is the…