为什么select()会在程序中消耗这么多CPU时间?

时间:2013-12-09 16:14:59

标签: java profiling selector mina

我有几个使用MINA的Java应用程序,它们都使用20个MINA线程。一个应用程序提供大约10,000个并发连接,这些连接通常是空闲的但有时会接收输对于该应用程序来说,20可能是一个合理的线程数,虽然我还没有完全描述它(这个问题正在进行中)。另一个应用程序一次只能提供大约15个连接,但启动IO工作,因此它们非常繁忙,无论如何都有20个MINA线程,这显然太多了。

对我来说很奇怪的是,两个应用程序总是把他们的CPU时间的30%,有时高达60%用于MINA的select()方法,在VisualVM中进行了分析。调用堆栈如下所示:

java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:228)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:81)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:87)
- locked <40ca5d54> (a sun.nio.ch.Util$2)
- locked <24649fe8> (a java.util.Collections$UnmodifiableSet)
- locked <3fae9662> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:98)
at org.apache.mina.transport.socket.nio.NioProcessor.select(NioProcessor.java:72)
at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1093)
at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)

这似乎是基于繁忙的民意调查,这对我来说听起来真的不对。

当我看到数字那么高时,我应该担心吗?是什么导致这个?这是我需要优化的东西还是更类似于睡眠或闲置程序?如果它更像是一个睡眠例程,它会以某种方式被安排为比其他CPU工作更低的优先级吗?

更新:this thread似乎是同一个问题。我遵循了它的建议,现在正在运行Java 1.7.0_45,但我仍然看到select在具有10k连接的应用程序中占用了高达90%的CPU时间。

我们正在使用MINA 2.0.4,这意味着this relevant bug已修复。

4 个答案:

答案 0 :(得分:8)

不幸的是,这是对数字的错误解释。

我已多次面对这种情况(并在stackoverflow也提出问题)。

主要原因是VisualVM没有显示正确的CPU时间。它显示了RUNNING状态中线程时间的百分比。但是来自Thread.State的文档:

  

可运行线程的线程状态。 runnable中的一个线程   状态正在Java虚拟机中执行,但它可能   等待操作系统中的其他资源   比如处理器。

这正是正在发生的事情。实际上,线程在OS epoll_wait()调用中被阻止。在Linux框中,有几种方法可以确认它的情况。

strace&#39}线程

$ strace -tttT -f -p [thread-id]

可以从jstack输出获取线程ID:

$ jstack [java-pid]
[...]
"Netty Builtin Server 1" #17 prio=5 os_prio=31 tid=0x00000001013dd800 nid=0xe12f runnable [0x0000700001fe4000]
  java.lang.Thread.State: RUNNABLE
  at sun.nio.ch.KQueueArrayWrapper.kevent0(Native Method)
  at sun.nio.ch.KQueueArrayWrapper.poll(KQueueArrayWrapper.java:198)
[...]

在这种情况下,线程id是0xe12f(应该转换为十进制)。您将看到线程将在epoll_wait()调用中的大部分时间。

pidstat线程

$ pidstat -tu -p [java-pid] | grep [thread pid]

你会看到这个线程的系统和用户CPU时间都很短,这意味着它不会占用CPU。

使用ps

轮询线程状态
$ ps -eL -o pid,tid,state | grep [thread-id]

您将看到大部分时间线程处于状态SSl(可中断睡眠)而不是R(可运行)。

如果服务没有运营问题,最后你不应该担心。

答案 1 :(得分:1)

一个应用程序轮询10,000个连接,每个连接使用非常少的CPU,但总之,它可以累计很多CPU时间。 所有优先事项都是先让其他工作排在第一位。

其他应用程序连接数较少但每个连接更多的紧缩也可以显示更高的百分比,但它应该显示较低的等待时间,以及更高的CPU分数。

答案 2 :(得分:1)

与所提到的链接问题的答案一样,常见的问题是较旧的JDK错误。由于您现在处于更新版本,我认为这实际上可能是硬件瓶颈。

这是一个玻璃鱼问题的链接,描述了他们讨论硬件(网络和服务器)成为问题根源的可能性。

https://www.java.net//forum/topic/glassfish/glassfish/glassfish-31-deadlock-epollarraywrapperepollwait-how-handle

此外,这是另一个类似的问题,但尚无答案: SelectorImpl is BLOCKED

答案 3 :(得分:1)

首先,两个应用程序都有相同的问题是好的;它可能表明问题出在JVM或OS上,而不是你的应用程序: - )

正如jzd所说,nio.select()有问题。 {各种版本的Java} x {各种平台,内核版本}的倍增使它看起来像是一个全面的问题。我希望其中一个适合你:

  • 如果您使用的是Linux,请尝试使用2.6内核以防您2.4

    ,假设该错误类似于:http://bugs.sun.com/view_bug.do?bug_id=6670302

  • 使用版本的JRE / JDK,而不是最新版本!

    ,即返回JRE 6 / JDK 6而不是7。

尝试

  • {旧版本的JRE(6),旧版Linux内核}或
  • {JRE(7)的更新版本,更新版本的Linux内核}

而不是像{较旧,较新}或{较新,较旧}一样混合起来。