この国では犬が

本と芝居とソフトウェア

あなたとスレッドダンプ - スレッドダンプ入門

去る 9 月 20 日(土)に、第八回 #渋谷java というイベントで LT してきました。(第八回 #渋谷java でスレッドダンプについてお話ししました - この国では犬が

あとあと自分で参照したり、スレッドダンプを知らない人に見せたりしたい内容でもあるので、ブログとしてまとめておきます。

スレッドダンプとはなにか

スレッドダンプは、ひとことで言うと「Java のスレッドのスナップショット」ということになります。

スレッドダンプを取得することで、取得した瞬間に JVM 上でどのような処理が実行されているのかを一覧して、調査することができます。
具体的には、その瞬間に存在している各スレッドの名前、状態、スタックトレース等を見ることができます。

スレッドダンプはなんの役に立つのか

プログラムが遅いとき・フリーズしたとき

一つには、プログラムが遅いとき・フリーズしたときの原因調査に役立ちます。

プログラムが遅いとき・フリーズしたときにスレッドダンプを取得すれば、各スレッドの状態や、どのメソッドを実行しているのかがわかります。
「どのスレッドがどのロックの解放を待っているのか」や、「動作が遅いとき、各スレッドはどのメソッドを実行しているのか」を調査できるわけです。

プログラムが「フリーズ」していても(ユーザの入力に応答しなくても)、スレッドダンプが取得できるのは、そのような場合でも JVM は「正常に動作」しているためです。
Java のプログラムは JVM 上で実行されます。よって、JVM 上で動作しているプログラムが「停止」しているような状態(たとえば、デッドロックが発生していたり、無限ループに陥っているかもしれません)でも、それを支える JVM は停止などしていないわけです。*1

プログラムがクラッシュしたとき

次に、プログラムがクラッシュしたときの原因調査にも役立ちます。

プログラムがクラッシュしたとき、JVM は自動的にスレッドダンプをファイルに出力します。
このスレッドダンプを調査することで、原因調査の足がかりとすることができます。たとえば、少なくとも、どのメソッドの実行中にクラッシュしたかがわかります。これだけでも、クラッシュした原因の調査には大きな助けとなるでしょう。

本番環境でも取得できる

最後に、スレッドダンプは本番環境でも取得できる、ということが挙げられます。

たとえば、プログラムが遅い原因を調べたかったら、hprof や VisualVM のようなプロファイルツール*2を使う、というのが一番楽をできる方法でしょう。
しかし、プロファイルツールはプログラム自体の動作に影響を与えてしまうため、本番環境では使えない場合がほとんどだと思います。
そのような場合でも、スレッドダンプはプログラムの動作にほとんど*3影響を与えないので、取得することが可能です。

Java の本番環境のトラブル調査では、スレッドダンプが活路となることが少なからずあります。

スレッドダンプの取り方

基本

スレッドダンプの取り方については、実はかなり丁寧に説明されたページが既に存在します。
ので、まずはそちらを紹介します。

このシリーズで、基本的な取り方、Linux の場合の注意点と対処方法、Windows サービスとして起動している場合の取得方法、までが解説されています。

サービス起動では jps + jstack

Windows サービスとして起動している場合には、先ほどのシリーズで紹介されている外部ツールを用いる方法よりも、JDK に含まれるツールである jps と jstack を使用した方法の方がよいかもしれません。 以下のページで、使い方が簡潔に紹介されています。

jps + jstack による取得は、もちろん Windows 以外の環境でも可能です。

サービス起動で権限がないときは PsExec

jstack を使用しても、「プロセスにアクセスできません」と言われてしまって、サービス起動のアプリケーションのスレッドダンプが取得できないケースがあるようです。
そのようなとき、Microsoft が提供しているツールである PsExec を使用する方法が下記ページで紹介されています。

ちなみにこのページは、いま僕がいる会社のユーザーフォーラムです。

スレッドダンプは何度か取る

スレッドダンプを取るときは、あいだを開けながら何度か取得しましょう。
そうすることで、各スレッドの状態遷移を調べることができて、調査の幅が広がります。

間隔は状況にもよりますが、数秒おき、といったところでしょうか。
もちろん、長いことフリーズしっぱなしというような場合には、数秒じゃ状況が動かないけれどもっと長い時間が経てば動く、ということもありうるので、可能であれば数分、あるいは数時間おきに取る、といったことも有効だと思います。(さすがに数時間フリーズしっぱなしにしておける本番環境というのもないでしょうが……)

画面のバッファサイズ

最後に、しょーもないことですが、一つだけ補足します。

Windows のコンソールで、出力をファイルにリダイレクトしていない状態でスレッドダンプを取らざるをえない場合は、忘れずにコマンドプロンプトのプロパティで「画面のバッファサイズ」を大きくしておきましょう。
スレッドダンプは結構な行数出力されるので、そうしないと上の方が切れて、無駄に焦ります……。

f:id:enk_enk:20140921213738p:plain

画像では高さだけ最大にしていますが、「幅」も大きくしておいてもいいかもしれません。

スレッドダンプの読み方

概要

スレッドダンプの読み方については、下手に文章で説明するより図で見たほうがかえってわかりやすい、と思う……ので、思い切って図でお送りします。

f:id:enk_enk:20140921223059p:plain

f:id:enk_enk:20140921223144p:plain

スレッドダンプ、むずかしそうに見えますが、意外と単純な作りですね。
ここから、1 ~ 2 行目の組成についてです。

f:id:enk_enk:20140921223240p:plain

f:id:enk_enk:20140921223247p:plain

f:id:enk_enk:20140921223250p:plain

f:id:enk_enk:20140921223253p:plain

f:id:enk_enk:20140921223259p:plain

f:id:enk_enk:20140921223333p:plain

f:id:enk_enk:20140921223418p:plain

はい。
「ネイティブスレッド ID」とか、そんなこと言われても……という要素も見受けられますが、実はこれらも別に怖れることなくて、スレッドダンプを読むとき、まずは下記の 3 つにだけ注目して、あとは(とりあえず)無視してしまってよい!……と思います。

f:id:enk_enk:20140921223559p:plain

スレッド名、スレッド ID、スレッドの状態、の 3 つです。
これくらいなら、僕にも読めそうな気がしてきます。

スレッド名

まずはスレッド名です。

スレッドにも色々あります。メインスレッド、書いたコードから直接生成したスレッド、フレームワークが生成するスレッド、JVM の内部スレッド……。

スレッド名を見て、いま発生している問題に関係の深そうなスレッドにあたりをつけることができます。
まずは注目するスレッドをスレッド名でざっと絞り込んで、それらのスレッドの状態(遷移)を足がかりに調査を始めていくことになります。

JVM の内部スレッドというのが案外多いので、スレッドの多さに圧倒されないようにしましょう。
たとえば、下記のようなものがあります。*4

  • CompilerThread
  • Finalizer
  • Reference Handler
  • VM Thread

自分が開発ないし運用しているアプリケーションの主要なスレッドの名前については予め把握しておくと、余計なスレッドに惑わされず、調査がスムーズにいくと思います。
自分が使っている JVM がデフォルトで生成するスレッドを(Hello World → Thread.Sleep() だけするアプリケーションのスレッドダンプを取るとかして)予め調べておくというのもよいかもしれません。

スレッド ID

続いて、スレッド ID(tid)も重要です。

というのも、スレッドダンプをあいだをあけながら何度か取得したとき、それぞれのスレッドダンプ間でのスレッドの同一性を保証するのがこのスレッド ID だからです。*5

スレッド ID をキーにして、スレッドの状態遷移を追跡することになります。

なのですが、こういうの、いかにも自動化したくなりますね。
というわけで、スレッドダンプの状態(遷移)を分析してくれる素敵なツールがわれわれの業界には存在します。

ありがたいことです。

スレッドの状態

最後に、というか、肝心の、スレッドの状態です。

スレッドの状態としては、そのスレッドの Thread.State が出力されています。
そのため、Javadoc を見ればそれぞれの状態の解説がバッチシ書いてあります。

ありがたいことです。

ありがたいことなのですが、僕はスレッドダンプの威容に惑わされて #渋谷java の発表資料作るまではこんなことにも気づきませんでした……。*6

スタックトレース

さて、1 ~ 2 行目についてはこんなところです。 スレッドダンプの残りは、スタックトレースということになります。

スタックトレースは、別に難しいことないですね。
各スレッドのコールスタックが下から上に向かって出力されている、何の変哲もないスタックトレースです。普通に読んでください。

……と言いたいところなのですが、スレッドダンプのスタックトレースを読むにあたっては、一つ注目すべきポイントがあります。

たとえば、先ほどの画像のスタックトレースには、こういう行がありました。

- locked<0x182d3880> (a org.apache.tomcat.util.net.JIoEndPoint$Worker)

これは、そのスレッドが 0x182d3880 という ID を持ったオブジェクト(型はorg.apache.tomcat.util.net.JIoEndPoint$Worker)のロックを取っているということを意味しています。

この情報が結構重要で、たとえば、別のスレッドの状態が BLOCKED で、そのスタックトレースにはこういう行が出力されているかもしれません。

- waiting to lock<0x182d3880> (a org.apache.tomcat.util.net.JIoEndPoint$Worker)

よく見ると、先ほどのものと ID が同じです。
このことから、このスレッドは先ほどのスレッドが取得しているロックの解放を待っている、ということがわかる、というわけです。

JVM ごとの違いについて

このエントリで説明に使用したスレッドダンプは、Java 7 のものです。
Java のバージョンが違ったり、JVM の開発元が異なる場合は、スレッドダンプの出力内容も異なってきます。

とはいえ、基本的には出力されている内容はだいたい同じで、フォーマットも似通っているため、ここで説明した知識を活用すれば読める、と考えてよいと思います。
わからないところがあればそこだけ調べれば、たぶんここに書いた知識と結びつけながら読むことができるはずです。

……と考えるきっかけになったのが先ほど紹介したブログのエントリの一部だったので、ここに引用させていただきます。

JVM の種類、バージョンにより若干フォーマットは異なりますが、だいたい似たような形式になります。
Sun、HP、Apple、BEAの VM のうちどれか一つ慣れた VM で解析経験を積んでおけば他の JVM のスレッドダンプを読むのに困ることはないでしょう。
ただし、IBMVM はかなり独特のフォーマットになっており独自の解析技術が必要になります。

ちなみに、もしこのエントリの続きが書かれていれば、僕がスレッドダンプについて LT をすることも、ブログを書くこともなかったでしょう……。笑

まとめ

スレッドダンプの使いみち、取り方、読み方について説明しました。

もう、スレッドダンプこわくないですね。
僕はこわくないです。

スレッドダンプは Java のありがたい機能です。
カジュアルにスレッドダンプ取って、それから読みましょう。*7

参考文献

スレッドについて参考になる書籍を紹介しておきます。

参考 Web ページ

数多くの Web ページを参考にさせていただいたのですが、なかでも特に役立ったものをリンクしておきます。(本文中で言及したものも含みます)

*1:もちろん JVM 自体が異常な状態にあるケースもありえなくはないですが、プログラムが「停止」しているケースの多くはそういうわけではないと思います

*2:便宜上プロファイルツールと言いましたが、VisualVM はプロファイルだけじゃなくて色々できるステキ便利ツールです

*3:明確な根拠(JVM の仕様、とか……)を持っていないのと、すべての状況でそう言えるとは限らないため、一応ほとんど、と書いていますが、まあ与えない、と考えていいと思います

*4:だいたい似たようなスレッドがあると考えてよいですが、JVM の種類やバージョンによって細目や名前は異なります

*5:実質的にはスレッド名でも追跡できる場合も多いと思いますが、かぶらないという保証は一切ないので、事故を防ぐためにスレッド ID を用いた方がよいでしょう

*6:まあ、幸か不幸か、読まざるをえないような機会もこれまではほぼなかったわけですが。これからは積極的に取って、読んでいきたいと思っています

*7:ただ、運用してるのが自分じゃない場合(特に会社が異なる場合とか、いっそのこと相手がエンドユーザの場合とか……)、「じゃ、スレッドダンプ取って」とカジュアルに頼むと怪訝な顔をされる可能性もあるので気をつけましょう。相手のスキルレベルにもよりますが、このエントリで紹介したような「取り方」も教えてあげると喜ばれる場合もあるかもしれません