Java线上问题排障:Linux内核bug引发JVM死锁导致线程假死

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://linuxstyle.blog.csdn.net/article/details/89637293

 Java本质上还是离不开操作系统,一来Java源码是用C/C++实现的,二来java进程还是需要依附于操作系统和硬件资源,有时候一些问题是操作系统级别导致的,下面的整个事件是源自一则真实的线上案例。

 

过程:

JVM死锁导致线程不可用,然后会瞬间起N个线程,当然起再多也是不可用的,因为需要的对象发生死锁,然后耗尽文件句柄导致外部请求也就是TCP连接无法建立产生拒绝服务,看起来就像线程假死了一样,不过巧合的是jstack之后就会恢复。

 

问题升级:

futex.c的bug->JVM死锁->起更多的线程->达到线程上限->新的请求无线程可以使用->拒绝服务

 

原因:

Linux内核某个switch分支缺少memory barrier的正确处理,导致外部应用如JVM的lock被错误锁住;一般jstack连后就恢复,当然你线上不能老是这样是不是,必须彻底解决这个问题。

 

解决办法:

方法一:上层解决替换中间件类库 ,比如httpclient的(前提是你是由此触发的)。

方法二:下沉解决方案前面已经说了给Linux内核打patch或者升级内核到比较稳定的新版本。

 

内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。 大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。

关于内存屏障参考:User-space RCU: Memory-barrier menagerie https://lwn.net/Articles/573436/

 

先看linux-2.6.33.1的代码\linux-2.6.33.1\linux-2.6.33.1\kernel\futex.c

然后再看Linus的修复记录: 

https://github.com/torvalds/linux/commit/76835b0ebf8a7fe85beb03c75121419a7dec52f0

很清楚的看到这个switch被加了default,以前是没有这个所以导致死锁的。

/*
 * Take a reference to the resource addressed by a key.
 * Can be called while holding spinlocks.
 *
 */
static void get_futex_key_refs(union futex_key *key)
{
	if (!key->both.ptr)
		return;

	switch (key->both.offset & (FUT_OFF_INODE|FUT_OFF_MMSHARED)) {
	case FUT_OFF_INODE:
		ihold(key->shared.inode); /* implies MB (B) */
		break;
	case FUT_OFF_MMSHARED:
		futex_get_mm(key); /* implies MB (B) */
		break;
	default:
		smp_mb(); /* explicit MB (B) */
	}
}

v3.18版修复 : 

 

 

futex: Ensure get_futex_key_refs() always implies a barrier

Commit b0c29f7 (futexes: Avoid taking the hb->lock if there's
nothing to wake up) changes the futex code to avoid taking a lock when
there are no waiters. This code has been subsequently fixed in commit
11d4616 (futex: revert back to the explicit waiter counting code).
Both the original commit and the fix-up rely on get_futex_key_refs() to
always imply a barrier.

However, for private futexes, none of the cases in the switch statement
of get_futex_key_refs() would be hit and the function completes without
a memory barrier as required before checking the "waiters" in
futex_wake() -> hb_waiters_pending(). The consequence is a race with a
thread waiting on a futex on another CPU, allowing the waker thread to
read "waiters == 0" while the waiter thread to have read "futex_val ==
locked" (in kernel).

Without this fix, the problem (user space deadlocks) can be seen with
Android bionic's mutex implementation on an arm64 multi-cluster system.

Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Reported-by: Matteo Franchin <Matteo.Franchin@arm.com>
Fixes: b0c29f7 (futexes: Avoid taking the hb->lock if there's nothing to wake up)
Acked-by: Davidlohr Bueso <dave@stgolabs.net>
Tested-by: Mike Galbraith <umgwanakikbuti@gmail.com>
Cc: <stable@vger.kernel.org>
Cc: Darren Hart <dvhart@linux.intel.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

futex:确保get_futex_key_refs()始终隐含屏障

提交b0c29f7(futexes:如果有的话,避免使用hb-> lock没有什么可以唤醒的)

更改futex代码以避免在什么时候锁定没有waiter。

此代码随后在提交中得到修复11d4616(futex:恢复显式waiter计数代码)。
原始提交和修复都依赖于get_futex_key_refs()总是意味着一个障碍。

但是,对于私有futexes,switch语句中没有任何一种情况
将触发get_futex_key_refs()并且函数完成
检查“waiter”之前需要的内存屏障futex_wake() - > hb_waiters_pending()。

结果是一场比赛,线程在另一个CPU上的futex上等待,允许waker线程读取“waiters == 0”,而waiter线程读取“futex_val ==锁定“(在内核中)。

如果没有此修复程序,可以看到问题(用户空间死锁)在arm64多集群系统上实现Android bionic的互斥锁。

 

下面是这个问题最初的发现和修复的讨论,是ARM公司的人员发现的。

 https://lore.kernel.org/patchwork/patch/508701/

 参考知乎上关于这个问题的讨论,类似的情况:

https://www.zhihu.com/search?type=content&q=jvm%E5%81%87%E6%AD%BB

 

https://ma.ttias.be/linux-futex_wait-bug/

 

想自己看看内核源码可以去:

https://mirrors.edge.kernel.org/pub/linux/kernel/

http://mirrors.163.com/kernel/linux/kernel/

 

 

展开阅读全文

性能与排障

05-29

<p>rn <span style="font-size:14px;"><strong>课程目标</strong></span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp;&nbsp;&nbsp;&nbsp;全面理解和掌握 Zabbix 监控系统的架构及运行原理,搭建和管理各种规模的Zabbix监控系统</span> rn</p>rn<p>rn <br />rn</p>rn<p>rn <span style="font-size:14px;"><strong>课程简介</strong></span><span style="font-size:14px;"></span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Zabbix是一个开源的企业级的监控解决方案。通过Zabbix可以监控IT基础设施的方方面面,包括硬件、操作系统、网络、虚拟化层、中间件和各种业务应用系统。用Zabbix几乎可以监控你想监控的任意数据。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp;&nbsp;</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 本课程从Zabbix的介绍、安装开始,一步步带你深入Zabbix,通过学习你会:</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 1、掌握Zabbix各个组件的配置和管理。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 2、掌握不同监控项的类型和配置方法,根据监控需求灵活配置监控项。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 3、掌握网络发现、低级发现和主动式agent自动注册,实现自动化监控。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 4、掌握模版、宏变量、触发器和告警通知的配置和高级的应用方法。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 5、掌握图形、屏幕、拓扑图和仪表盘等数据可视化的方法,利用大屏可以实时的展示监控数据。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 6、掌握Zabbix系统自身的维护、备份、升级、排障以及性能优化。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 7、掌握Zabbix内部运行机制和Zabbix使用技巧,让zabbix更好的帮助你实现监控目标。</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span> rn</p>rn<p>rn <span style="font-size:14px;">&nbsp; &nbsp; &nbsp; &nbsp; 本课程中还包含很多操作演示,比如像创建主机,创建监控项、触发器、图形、全局事件关联等,也介绍了微信和钉钉告警的配置方法。当你对Zabbix深入了解之后,面对层出不穷的新业务、新应用,你都能轻松自如的制定和提供相应的监控解决方案。</span> rn</p>rn<p>rn <br />rn</p>rn<p>rn <br />rn</p>rn<p>rn <br />rn</p>rn<p>rn <strong><span style="color:#C00000;font-size:14px;">特别提示:官网的中文文档有些地方翻译的有问题,一定要以英文文档为准。</span></strong> rn</p>rn<span style="font-size:14px;"></span>

没有更多推荐了,返回首页