応答しないアプリケーションのデバッグ

他の言語: English Español 한국어 Português 中文

デバッガのチュートリアルはたくさんあり、ラインブレークポイントの設定方法や、値のログ出力、式の評価方法を教えてくれます。 この知識だけでもアプリケーションをデバッグするための多くのツールが手に入りますが、 実際のシナリオではもっと複雑なアプローチが必要になることがあります。

この記事では、プロジェクトの詳細な知識がほとんどなくてもUIフリーズを引き起こすコードをどのように探し出し、 その場で不具合のあるコードを修正する方法について学びます。

問題

具体的な手順に従いたい場合は、こちらのリポジトリをクローンしてください: https://github.com/flounder4130/debugger-example

あるアクションを実行するときに応答しなくなる複雑なアプリケーションがあるとしましょう。 バグの再現方法はわかっていますが、問題のある機能を担当するコードの部分がどこにあるのかがわからないという難しさがあります。

サンプルアプリケーションのUIには、いくつかのアクションを実行するための多数のボタンがあります

我々の例では、Button Nをクリックするとフリーズが発生します。 しかし、このアクションを担当するコードを見つけるのはそう簡単ではありません:

プロジェクト全体で'Button N'を検索しても結果はありません プロジェクト全体で'Button N'を検索しても結果はありません

では、デバッガを使ってどのようにそれを見つけるかを見てみましょう。

メソッドブレークポイント

メソッドブレークポイントの利点は、ラインブレークポイントと比べてクラスの階層全体に対して使用できることです。 これが役立つ理由は何でしょうか?

サンプルプロジェクトを見てみると、すべてのアクションクラスは、 Action インタフェースから派生しており、 perform() という単一のメソッドがあります。

メソッドブレークポイントアイコンがエディターのガターに表示されます メソッドブレークポイントアイコンがエディターのガターに表示されます

このインターフェースのメソッドにメソッドブレークポイントを設定すると、派生メソッドのいずれかが呼び出されるたびにアプリケーションが中断されます。 メソッドブレークポイントを設定するには、メソッドを宣言している行をクリックします。

デバッガセッションを開始し、Button Nをクリックします。すると、アプリケーションは ActionImpl14 で中断されます。 これで、このボタンに対応するコードがどこにあるかがわかりました。

アプリケーションはActionインターフェースを実装するクラスで中断されました アプリケーションはActionインターフェースを実装するクラスで中断されました

この記事ではバグの発見に焦点を当てていますが、このテクニックは、大規模なコードベースで何かがどのように動作しているかを理解したいときにも時間を節約できます。

アプリケーションの一時停止

メソッドブレークポイントのアプローチは良好に動作しますが、親インターフェースについて何かを知っているという前提に基づいています。 この前提が間違っていたり、他の何かの理由でこのアプローチを使用できない場合、どうすればよいでしょうか?

実際、私たちはブレークポイントなしでさえもそれを行うことができます。 Button Nをクリックし、アプリケーションがフリーズしている間、IntelliJ IDEAに戻ります。 メインメニューから 実行 (Run) | デバッグアクション (Debugging Actions) | 時停止 (Pause Program)を選択します。

メインスレッドのコールスタックは現在何をしているのかを表示します メインスレッドのコールスタックは現在何をしているのかを表示します

アプリケーションは中断され、スレッドと変数 (Threads & Variables)タブで現在のスレッドの状態を調べることができます。 これにより、アプリケーションが現在何を行っているかが理解できます。アプリケーションがフリーズしているため、フリーズしているメソッドを識別し、それを呼び出し元に遡ることができます。

このアプローチには、すぐに説明するより伝統的なスレッドダンプよりもいくつかの利点があります。 例えば、便利な形で変数に関する情報を提供し、プログラムの実行を制御することができます。

スレッドダンプ

最後に、スレッドダンプを使えば、これは厳密にはデバッガの機能ではありません。 デバッガを使っているかどうかに関係なく、利用可能です。

Button Nをクリックします。アプリケーションがフリーズしている間、IntelliJ IDEAに戻ります。 メインメニューから実行 (Run) | デバッグアクション (Debugging Actions) | スレッドダンプを取得 (Get Thread Dump)を選択します。

左側にあるスレッドをスキャンし、AWT-EventQueueで問題の原因が何であるかを確認します。

IntelliJ IDEAのスレッドダンプビューア IntelliJ IDEAのスレッドダンプビューア

スレッドダンプの欠点は、プログラムの状態のスナップショットを提供するだけで、 それが作成された時点のものだけを提供するため、スレッドダンプを使用して変数を探索したり、プログラムの実行を制御することはできません。

私たちの例では、スレッドダンプに頼る必要はありません。しかし、他のケース、例えばデバッグエージェントなしで起動されたアプリケーションをデバッグしようとしているときにこの手法が役立つ可能性があるため、これについても触れておきたかったのです。

問題の理解

いずれのデバッグ手法を使用しても、 ActionImpl14 に到達します。 このクラスでは、誰かが作業を別のスレッドで実行しようと意図していましたが、 Thread.start() Thread.run() を混同してしまいました。 後者は、呼び出し元のコードと同じスレッドでコードを実行します。

IntelliJ IDEA の静的アナライザーは、設計時にすでにこのことを警告しています:

IntelliJ IDEAの静的分析では、Thread.run()への疑わしい呼び出しについて警告が表示されます IntelliJ IDEAの静的分析では、Thread.run()への疑わしい呼び出しについて警告が表示されます

重い処理(または重いスリープ)を行うメソッドがUIスレッド上で呼び出され、 メソッドが終了するまでUIスレッドをブロックします。そのため、Button Nをクリックした後、しばらくの間、UIで何もできません。

HotSwap

バグの原因を発見したので、問題を修正しましょう。

プログラムを停止し、コードを再コンパイルし、再実行することもできます。 しかし、小さな変更だけで全体のアプリケーションを再デプロイするのは必ずしも便利ではありません。

賢くやってみましょう。まず、提案されたクイックフィックスを使用してコードを修正します:

コンテキストメニュー(Alt-Enter)では、疑わしいコードを修正するオプションが表示されます コンテキストメニュー(Alt-Enter)では、疑わしいコードを修正するオプションが表示されます

コードが使える状態になったら、実行 (Run) | デバッグアクション (Debugging Actions) | 変更したクラスの再ロード (Reload Changed Classes)をクリックします。 新しいコードがVMに適用されたことを確認するバルーンが表示されます。

ランタイムに更新されたクラスが到達したことを確認するバルーン ランタイムに更新されたクラスが到達したことを確認するバルーン

それではアプリケーションに戻り、確認してみましょう。Button Nをクリックしても、もうアプリケーションはフリーズしません。

Tip icon

HotSwapには制限があることを覚えておいてください。 拡張されたHotSwap機能に興味がある場合は、DCEVMJRebelなどの高度なツールを参照するとよいでしょう。

まとめ

私たちの推理とデバッガのいくつかの機能を使って、プロジェクトでUIフリーズを引き起こしていたコードを特定できました。 そして、再コンパイルや再デプロイに時間をかけることなく、コードを修正しました。これは、現実のプロジェクトでは時間がかかる場合があります。

説明した技術が役立つことを願っています。皆さんの意見を聞かせてください!

これからもお楽しみに!

all posts ->