这篇blog的问题不能算是解决,仅仅只是一种分析和猜测,后续的一些行动可能会证明一些猜想,也可能什么都解决不了。如果有和我相同情况的同学,也知道是什么问题造成的,请不吝赐教。
问题:
上周周末,没有和同事们出去Outing,在家管孩子,去生产环境观察了一下集群机器的当前运行状态,发现应用在这些多核机器上压力极端不均匀。
Top一下大致状态如下:
峰值的时候,单CPU的使用率都到了80%,这种情况对于多核服务器来说是很不正常的使用。对于Java的开发者来说,多线程编程是无法控制线程如何在CPU上分配的,因为Java本身不实现线程机制,说是跨平台的语言,但是性能及特性会根据操作系统的实现有很大的差异,因此Java调优有时候需要对系统配置甚至内核作调优。
分析:
首先在测试环境下作了多次同样的压力测试,尝试了与线上一样的操作系统版本,相似的配置,但测试结果却是负载分配很均匀。
此时重新启动了一台问题机器,发现负载降下来了,同时也很均衡,也就是说在当前的压力下不应该有这样高的cpu消耗,同时也排除了硬件或者操作系统的一些配置问题。
在CPU满负荷的情况下,很多时候会认为应该是循环造成的,对于单个CPU的消耗更是。通过Top H查看具体到底哪一个线程会长时间消耗CPU。
可以看到PID为13659的线程是“罪魁祸首”,但13659究竟在干什么,是应用的线程还是系统的线程,是否是陷入了死循环,不得而知。接着就按照Java的土办法,Kill -3 pid,然后看看输出日志。
根据线程号来查找dump出来的日志中nid,发现这个线程是VM Thread,也就是虚拟机线程。(这里作一下转换,将13659转换成为16进制就是0x355b)
用pstack看了一下这个线程的工作,结果如下:
Thread 2074 (Thread 1846541216 (LWP 13659)):
#0 0x0659fa65 in ObjectSynchronizer::deflate_idle_monitors ()
#1 0x065606e5 in SafepointSynchronize::begin ()
#2 0x06613e83 in VMThread::loop ()
#3 0x06613a6f in VMThread::run ()
#4 0x06506709 in java_start ()
#5 0x00aae3cc in start_thread () from /lib/tls/libpthread.so.0
#6 0x00a1896e in clone () from /lib/tls/libc.so.6
搜索了一下ObjectSynchronizer::deflate_idle_monitors,发现了sun的bug库中有bug关于jdk1.6中由于这个方法导致运行期问题的说法:http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=803cb2d95886bffffffff9a626d3b9b28573?bug_id=6781744
然后就直接去openjdk官方网站去查找这个类的代码,大致了解一下他的作用,具体的代码链接如下:http://xref.jsecurity.net/openjdk-6/langtools/db/d8b/synchronizer_8cpp-source.html
主要工作应该是对资源对象的回收,在加上pstack的结果,应该大致知道是对线程资源的管理。但具体代码就没有进一步分析了。
接着就分析一下自己的应用:
压力测试(高强度、长时间)都做过,没有发现什么异常。
本身应用是否会存在的缺陷导致问题呢。有人说VM Thread兼顾着GC的工作,因此内存泄露,对象长期积压过多也可能影响,但其实在dump的结果可以看到,GC有单独的工作线程,同时我也观察到GC这些线程的工作时间长度,因此由于GC繁忙导致CPU上去,基本上来说可以排除。
其次在SIP项目中使用了JDK的线程池(ExecutorService)和LinkedBlockingQueue。后者以前的文章里面提到在1.5版本里使用poll方法会有内存泄露,到1.6虽然没有内存泄露,但是临时锁对象增长的很快,会导致GC的频度增加。
行动:
上面零零散散的一些分析,最终让我决定有如下的行动:
1. 升级某一台服务器的JDK,当前是1.6.0_10-b33,打算升级到1.6的14版本。比较观察多台机器的表现,看是否升级了JDK可以解决问题。
2. 去除LinkedBlockingQueue作为消息队列,直接由生产者将生产结果按照算法分配给消费者线程,避免竞争,锁的消耗,同时也防止LinkedBlockingQueue带来的资源消耗。
3. 测试环境继续作长时间的压力测试,同时可以结合Jprofile之类的工具来分析长时间后可能出现的问题。
后话:
这年头真的啥都要学一点,求人不如求己。
SA,DBA,测试都需要能够去学习一些,起码在初期排查问题上自己能够做点啥,要不然别人也忙,自己又无从下手。就好比这次压力测试好不容易排上队,但是还是满足不了及时上线的需求,因此自己去LoadRunner压,好歹给出一个零时的报告先大家看着。应用的异常有时候是应用本身设计问题,也可能是开发语言的问题,也可能是操作系统的问题,因此要去定位这种比较复杂的问题,真的需要有耐心去好好的学习各种知识,现在看来知识还是匮乏啊,要不然就可以分析出openjdk中可能存在的问题。