web-dev-qa-db-ja.com

select()がプログラムでCPU時間を大量に消費するのはなぜですか?

MINAを使用するJavaアプリケーションがいくつかあり、それらはすべて20のMINAスレッドを使用します。1つのアプリケーションは約10,000の同時接続を処理し、通常はアイドル状態ですが、入力を受け取ることがあります。そのアプリケーションについては、正確にプロファイルしていませんが(この質問が出ています)、別のアプリケーションは一度に約15の接続しか提供しませんが、IO作業を開始するため、非常にビジーです。 、とにかく20のMINAスレッドがありますが、これは明らかに多すぎます。

私にとって奇妙なのは、両方のアプリケーションが常にCPU時間の約30%、時には60%を、VisualVMでプロファイルされたMINAのselect()メソッドに費やしていることです。コールスタックは次のようになります。

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作業よりも優先度が低くなるようにスケジュールされていますか?

更新: このスレッド 同じ問題のようです。私はそのアドバイスに従い、現在Java 1.7.0_45を実行していますが、10k接続のアプリケーションでselectがCPU時間の90%を占めているのをまだ見ています。

MINA 2.0.4を使用しています。つまり、 この関連するバグ が修正されています。

29
djechlin

残念ながら、これは数字の誤った解釈です。

私は何度もこの状況に直面しました(そして stackoverflow にも質問します)。

主な理由は、VisualVMが正しいCPU時間を表示しないことです。 RUNNING状態のスレッド時間のパーセンテージを示しています。しかし、_Thread.State_のドキュメントから:

実行可能なスレッドのスレッド状態。実行可能状態のスレッドはJava仮想マシンで実行されていますが、オペレーティングシステムからの他のリソースを待機している可能性がありますプロセッサなど。

これはまさに起こっていることです。実際、スレッドはOS epoll_wait()呼び出し内でブロックされています。 Linuxボックスでは、それが事実であることを確認する方法がいくつかあります。

strace'ingスレッド

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

スレッドIDはjstack出力から取得できます。

_$ 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_です(10進数に変換する必要があります)。ほとんどの場合、スレッドはepoll_wait()呼び出しにあります。

pidstatingスレッド

_$ pidstat -tu -p [Java-pid] | grep [thread pid]
_

このスレッドにより、システムとユーザーのCPU時間が少なくなり、CPUを消費しなくなります。

psを使用したスレッド状態のポーリング

_$ ps -eL -o pid,tid,state | grep [thread-id]
_

ほとんどの場合、スレッドはS(実行可能)ではなく、状態SlまたはR(中断可能なスリープ)になります。

結局、サービスに運用上の問題がなければ、それについて心配する必要はありません。

12
Denis Bazhenov

参照されているリンクされた質問からの回答と同様に、一般的な問題は古いJDKのバグです。現在、更新されたバージョンを使用しているため、これは実際にはハードウェアのボトルネックである可能性があると思います。

これは、ハードウェア(ネットワークとサーバー)が問題の原因である可能性について説明しているGlassfishの問題へのリンクです。

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

また、これはまだ答えのない別の同様の質問です: SelectorImplはブロックされています

1
jzd

まず、両方のアプリケーションで同じ問題が発生するのは良いことです。おそらく、問題がアプリケーションではなく、JVMまたはOSのいずれかにあることを示しています:-)

Jzdが述べたように、nio.select()には問題がありました。 {Javaのさまざまなバージョン} x {さまざまなプラットフォーム、カーネルバージョン}の乗算は、あらゆる場所の問題のように見えます。私はあなたのためにこれらの作品の1つを願っています:

  • Linuxを使用している場合は、2.6を使用している場合に備えて、2.4カーネルを試してください。

    、バグが次のようになっていると仮定します: http://bugs.Sun.com/view_bug.do?bug_id=6670302

  • 最新バージョンではなく、古いバージョンのJRE/JDKを使用してください!

    、つまり、7ではなくJRE 6/JDK6に戻ります。

試してみてください

  • {古いバージョンのJRE(6)、古いバージョンのLinuxカーネル}または
  • {新しいバージョンのJRE(7)、新しいバージョンのLinuxカーネル}

{older、newer}や{newer、older}のようにそれらを混同する代わりに。

1
vijucat

1つのアプリが10,000の接続をポーリングし、接続ごとに使用するCPUはごくわずかですが、合計するとCPU時間のかなりの部分になる可能性があります。すべての優先事項は、他のジョブを最初に並べることです。

接続数は少ないが接続あたりのクランチが多い他のアプリでも、高い割合を示す可能性がありますが、待機時間の割合が低く、CPUの割合が高いはずです。

1
Mike Dunlavey