某些类型的 bug 经常落到性能调优师手中来进行修复,虽然严格地讲,它们算不上是性能问题。通常由对象泄漏造成的内存不足就是这类 bug 中的一个。(在本专栏前面的一期中介绍过如何在“垃圾对话(Trash talk)”中处理这些问题,请参阅 参考资料。)另外一类经常落到性能调优师手上修补的 bug 就是线程死锁和其他线程方面的问题,例如竟态条件,因为这些问题一般只在对程序进行负载测试时才会表现出来。
将这些 bug 交到性能调优师手中通常有很好的理由:识别和清除性能及内存瓶颈所需要的工具,与识别对象泄漏和竟态条件的工具相同。死锁相对来说比较容易识别;只要注意到应用程序冻结,就会有堆栈跟踪显示是哪套线程锁定了其他线程的监视器。但是不幸的是,对于竟态条件,有可能更加无从下手。
等待泄漏
有一类叫做 等待泄漏 的竟态条件最近受到我们的关注。基本的问题是,在使用 wait/notify 的概念时,通常会有一个或多个线程阻塞在 wait() 调用中,等待着另外一个线程通知它某个条件已经为真,这样它才能退出 wait() 调用,并继续进行处理。通知线程调用 notify() 或 notifyAll() 方法来通知等待线程现在就可以苏醒并继续进行处理。
这种方法很显然会形成竟态条件,但是一直到最近,我们在实践中都没有看到过这种情况。如果进入等待状态,等待特定资源变得可用,但是另外一个线程调用 notify() 正好是 在 您进入等待状态 之前 进行的,那么会发生什么呢?结果是:即使资源可用,线程也会陷入等待状态。
当然,有许多解决方案来避免这种场景 —— 毕竟,这是一个与其他 bug 类似的 bug。显然您应当更加仔细,在进入等待状态之前,判断资源是否可用。更具体来说,您应当检查资源在同步块内部是否可用,而不应当在资源可用的时候进入等待状态(这是推荐的方案,但这可能是一种可伸缩性较差的解决方案),或者也可以用 JDK 5.0 中可以使用的一些更复杂的同步类和相关的技术(参阅 参考资料)。
等待泄漏显然是个 bug,但是在这里要关心的不是对这个问题的解决方案,而是发现问题的方法。在拥有成百上千个线程的复杂应用程序中,除非事先发现故障现象,否则很难找出等待泄漏。与死锁不同,这里没有明显的能够说明问题的证据(例如两个线程相互等待对方锁定的监视器)。相反,会有大量线程滞留在 Object.wait() 调用中,对于许多应用程序来说,这是非常正常的情况。
模拟一个等待泄漏
学习如何发现等待泄漏的最好方法是实际查看一个泄漏,并理解导致它的原因。清单 1 演示了一个非常简单的等待泄漏。 WaitLeak 类实现了 Runnable,每个线程要停下来等待,直到得到通知,然后终止。在这个模拟中,启动了 4 个 WaitLeak 线程,每秒钟启动一个。另一个类 WaitLeakNotifier 通知所有在 WaitLeak wait() 调用中等待的线程,然后终止。主方法接受一个参数,该参数表示 WaitLeakNotifier 通知所有等待线程之前等待的毫秒数。
清单 1. 等待泄漏模拟类
public class WaitLeak implements Runnable
{
public
static Object LOCK = new Object();
public static void main(String[] args)
throws Exception
{
int WAITTIME = Integer.parseInt(args[0]);
int NUMTHREADS = 4;
(new Thread(new WaitLeakNotifier(WAITTIME))).start();
for (int i = 0; i < NUMTHREADS; i++)
{
Thread.sleep(1000);
(new Thread(new WaitLeak())).start();
}
}
public void run()
{
System.out.println("Starting thread " + Thread.currentThread());
synchronized(LOCK)
{
try{
LOCK.wait();
} catch(InterruptedException e) {}
}
System.out.println("Terminating thread " + Thread.currentThread());
}
}
class WaitLeakNotifier implements Runnable
{
long waittime;
public WaitLeakNotifier(long time)
{
waittime = time;
}
public void run()
{
long now = System.currentTimeMillis();
long diff = 0;
while( (diff = System.currentTimeMillis() - now) < waittime)
{
try {
Thread.sleep(waittime - diff);
} catch(InterruptedException e){}
}
synchronized(WaitLeak.LOCK)
{
WaitLeak.LOCK.notifyAll();
}
}
}
实现竟态条件
图 1 显示了三种可能的场景,在通知发送之前,它们所用的延迟不同。
顶部的面板显示了延迟相对较大(例如 10 秒)的程序,如下所示:
java WaitLeak 10000
这种方式造成所有 4 个 WaitLeak 线程均启动、等待、在 10 秒钟后得到通知,然后终止。
图 1 的第 2 个面板显示的程序,它的延迟为 WaitLeak 启动一半的时候,如 2 或 3 秒:
java WaitLeak 2000
在这个场景中,比通知线程启动得早的 WaitLeak 线程得到通知并终止,但是在通知发送之后启动的 WaitLeak 线程会一直等下去。
第 3 个场景的延迟时间非常短(例如 1 毫秒),如下所示,效果如图 1 的第 3 个面板所示。
java WaitLeak 1
图 1. 等待泄漏实战
在这个例子中, WaitLeakNotifier 在其他线程启动之前发送通知。所以没有线程会从 WaitLeakNotifier 得到通知,从而造成所有线程都一直阻塞在等待状态。
清单 2 显示了启动几分钟后的堆栈跟踪,截取自第 2 个场景。(可以在 Windows 上按 Ctrl+Break 得到堆栈跟踪,然后在 Unix 上用 Ctrl+\,或者向进程发送 kill -3。)
清单 2. java WaitLeak 2000 的线程堆栈转储
"Thread-4" prio=5 tid=0x00a0eee8 nid=0xf04 in Object.wait() [2d1f000..2d1fd8c]
at java.lang.Object.wait(Native Method)
- waiting on <0x1002c780> (a java.lang.Object)
at java.lang.Object.wait(Unknown Source)
at WaitLeak.run(WaitLeak.java:25)
- locked <0x1002c780> (a java.lang.Object)
at java.lang.Thread.run(Unknown Source)
"Thread-3" prio=5 tid=0x00a0c418 nid=0xc5c in Object.wait() [2cdf000..2cdfd8c]
at java.lang.Object.wait(Native Method)
- waiting on <0x1002c780> (a java.lang.Object)
at java.lang.Object.wait(Unknown Source)
at WaitLeak.run(WaitLeak.java:25)
- locked <0x1002c780> (a java.lang.Object)
at java.lang.Thread.run(Unknown Source)
"Thread-2" prio=5 tid=0x00a0d7a0 nid=0x118c in Object.wait() [2c9f000..2c9fd8c]
at java.lang.Object.wait(Native Method)
- waiting on <0x1002c780> (a java.lang.Object)
at java.lang.Object.wait(Unknown Source)
at WaitLeak.run(WaitLeak.java:25)
- locked <0x1002c780> (a java.lang.Object)
at java.lang.Thread.run(Unknown Source)
发现等待泄漏
线程转储显示了等待泄漏的故障现象,但是重要的东西却从线程转储中漏掉了 —— 就是 没有 通知等待线程的那个线程。所以需要添加一些额外的上下文信息,以便协助识别出等待泄漏。通常,可能报告出两种故障模型 —— 死锁,或应用程序响应程度逐渐下降。
首先来考虑标准的死锁类型的问题报告:应用程序什么也不做(虽然对用户引发的事件可能仍然有响应)—— 应用程序部分或者完全冻结。等待泄漏的故障现象与普通的死锁报告类似,不同之处在于在堆栈转储中没有死锁的迹象。如果看到这种情况,就应当考虑可能是遇到了等待泄漏。
第 2 个场景是一个逐渐过载、响应越来越差的应用程序。在这个例子中,随着时间的推移,越来越多的线程进入等待泄漏状态,这意味着越来越多的线程(本来应当做事的)只是闲在那里,什么都不做。结果就是,应用程序被傻等着永远不会到来的通知的那些线程所阻塞。结果有些资源被耗尽 —— 可能是线程池用尽、或者是多过的线程导致内存不足错误、或者由于应用程序最终出现了与第一类死锁类型相同的故障现象的情况。这可能是一个比较容易诊断的等待泄漏,因为可以比较某一段时间内的堆栈转储,并查看某些特定的 Object.wait() 堆栈(可能在使用同一个锁)的数量是否一直持续增长。刚才看到的生产示例中有一个响应越来越慢的服务器,到最后,仅仅在几分种之后,43 个等待泄漏堆栈就变成了 108 个等待堆栈,很快服务器就不再响应任何请求。
结束语
有趣的是,我们并不相信有什么可以自动发现等待泄漏的方法,除非只有等待泄漏线程被遗忘(就像本文中的模拟情况,但是在真正的应用程序中很少有同类情况)。实际上,多数情况下很难确定哪个应该调用 notify() 的代码绝对不会因为被锁定的监视器而再次执行。所以手工检查可能是我们能做的最好方式 —— 而这正是本文的目标,在您的性能调优武器库中加上另一个工具。如果您偶然碰到等待泄漏,我们敢保证您早晚会认出它来,我们希望本文能尽早给您带来帮助。
转自http://www.ibm.com/developerworks/cn/java/j-perf01215/
分享到:
相关推荐
往往做项目的时候情况非常复杂,或者项目做得差不多了想起来要性能优化检查下内存泄露。 如何找到项目中存在的内存泄露的这些地方呢?
性能测试之内存泄露篇,详细讲解内存泄露相关判断方法以及测试方案
opencv3和opencv4多线程内存泄漏问题:以cv::resize函数测试结果为例。 使用中可修复或者可避免内存泄漏:1)使用opencv2的版本;2)在代码中设置修复该问题.
Conditional Coverage Analysis: 分析特定条件所覆盖的代码范围,包括含有多个条件语句的代码行; Filter Catch Blocks: 更精确的覆盖范围报告; 批处理模式: 可通过批处理方式运行,简化与夜间编译/测试系统的...
C和C++语言是我司的主流编程语言,然而C/C++具有很多强大的语言特性,从而导致C/C++非常复杂,使得代码更容易出现BUG、难以阅读和维护。
由于该村庄附近有生产煤矿,地表泄露的瓦斯有来自于开采煤层的可能性,为确认瓦斯来源,分析瓦斯泄漏原因以便采取有效措施,进行了地面泄漏气体与井下采空区瓦斯气体相关性取样实验及煤矿开采对地下水影响的分析,...
评估性能指标:验证系统性能是否满足目前生产要求,能否保证系统在生产环境的业务处理需求和用户量下,系统性能满足性能要求; 发现性能拐点:逐步加大负载,发现系统的瓶颈或者不能接收的性能点作为拐点,并...
我们将用C语言实现类似的功能,从而更好地理解auto的原理和使用方法。 接着,我们将介绍lambda表达式,它可以定义匿名函数,使得代码更加简洁和易读。我们将用C语言实现类似的功能,从而更好地理解lambda表达式的...
jmap+EclipseMAT:排查内存泄漏的好工具.pdf
ajax js性能优化和内存泄露检测工具
Conditional Coverage Analysis: 分析特定条件所覆盖的代码范围,包括含有多个条件语句的代码行; Filter Catch Blocks: 更精确的覆盖范围报告; 批处理模式: 可通过批处理方式运行,简化与夜间编译/测试系统的...
随着移动互联网向纵深发展,用户变得越来越关心应用的体验,开发者必须关注应用性能所带来的用户流失问题。 据统计,有十种应用性能问题危害最大,分别为:连接超时、闪退、卡顿、崩溃、黑白屏、网络劫持、交互性能...
C内存管理内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但c,除非放弃C++,转到Java或者.NET,他们的...
关于android内存泄露的性能优化的PPT说明,解释以及建议解决的办法
《App个人信息泄露情况调查报告》:八成被访者曾遭遇过个人信息泄露.pdf《App个人信息泄露情况调查报告》:八成被访者曾遭遇过个人信息泄露.pdf《App个人信息泄露情况调查报告》:八成被访者曾遭遇过个人信息泄露....
持续更新github地址 ... ...OOMDetector是一个iOS内存监控组件,应用此组件可以帮助你轻松实现OOM监控、大内存分配监控、内存泄漏检测等功能。...3.内存泄漏检测:可检测OC对象、Malloc堆内存泄漏,提供泄漏堆栈信息
本文对防治煤矿有毒有害气体泄漏...实验结果表明,该密封材料可以满足煤矿生产条件下所要求的力学性能和较高的粘结性能,并且具有发泡迅速、发泡倍数高、泡沫尺寸稳定等特点,可很好地应用于煤矿有毒有害气体泄漏的防治。
websphere 性能分析 及内存泄漏
Conditional Coverage Analysis: 分析特定条件所覆盖的代码范围,包括含有多个条件语句的代码行; Filter Catch Blocks: 更精确的覆盖范围报告; 批处理模式: 可通过批处理方式运行,简化与夜间编译/测试系统的...
理解和探查内存不足内存泄漏 了解Java基本内存管理基本概念 了解发生内存不足/内存泄漏错误的原因和症状 了解如何诊断内存不足/内存泄漏错误 了解如何解决内存不足/内存泄漏错误