Javaアプリケーションで例外(java.lang.Throwableのインスタンス)がスローされた場合などに出力されるスタックトレースは、エラーが発生するまでの経緯(メソッドの呼び出し順番)が示されています。このスタックトレースを解析することにより、エラーが発生した箇所と原因を確認することができます。
スタックトレースの出力先
スタックトレースの出力先は、標準エラーです。通常のJavaアプリケーションの場合は、コンソールに出力されますが、Servlet/JSP/EJBアプリケーションの場合は、ログファイル(標準出力、標準エラー出力あるいはJava VMの出力を格納するファイルなど)に出力されます。
スタックトレースの出力方法
Javaでスローされた例外をcatch節でキャッチし、例外のprintStackTraceメソッドを実行することにより、スタックトレースを出力することができます。
java.lang.Throwable.printStackTrace()でスタックトレースを出力する方法を、図1に示します。
図1 printStackTraceメソッドでスタックトレースを出力する方法
try {
SampleBMPSessionRemote bmpSessionRemote = bmpSessionHome.create();
} catch(Exception e) {
e.printStackTrace();
}
なお、スローされた例外をtry-catch構文で処理するメソッドがスレッドにない場合、そのスレッドは停止され、Java VMによってスタックトレースが出力されます。
スタックトレースの出力フォーマット
スタックトレースの出力フォーマットを、図1に示します。
図1 スタックトレースの出力フォーマット
例外クラス名: エラーメッセージ at クラス名.メソッド名1(ソース名:行番号) 呼び出し先 at クラス名.メソッド名2(ソース名:行番号) : : at クラス名.メソッド名N(ソース名:行番号) 呼び出し元
最初の1行目は、スローされた例外のクラス名とエラーメッセージです。
エラーメッセージがない場合もあります。
2行目以降は、メソッドの呼び出し元(クラス名.メソッド名N)から呼び出し先(クラス名.メソッド名1)に向かって下から上に出力されます。
2行目(クラス名.メソッド名1)が、例外をスローしたメソッドの情報です。
“メソッド名”が“<init>”の場合、コンストラクタを示します。
“メソッド名”が“<clinit>”の場合、static initializerを示します。
“(ソース名:行番号)”が“(Native Method)”の場合、Javaのネイティブメソッド(.soや.dllファイル)を示します。
クラスのコンパイル時にデバッグ情報を削除した場合、“(ソース名:行番号)”には、ソース名しか表示されなかったり、“Unknown Source”と表示されたりする場合があります。
図1の出力例をもとにして、解析方法を説明します。
図1の先頭の“数字:”は、説明の便宜上、付加しています。
図1 スタックトレースの出力例
1:java.lang.NullPointerException 2: at agency.attestation.CheckLoginInfo.doCheck(CheckLoginInfo.java:150) 3: at agency.attestation.AttestationServlet.doGet(AttestationServlet.java:96) 4: at agency.attestation.AttestationServlet.doPost(AttestationServlet.java:161) 5: at javax.servlet.http.HttpServlet.service(HttpServlet.java:772) 6: at javax.servlet.http.HttpServlet.service(HttpServlet.java:865) :
読み方
図1のスタックトレースは、6行目から上方向に読むと、次の流れで例外が発生したことがわかります。
javax.servlet.http.HttpServlet.service()が、HttpServlet.javaの865行目で、javax.servlet.http.HttpServlet.service()を実行し、
javax.servlet.http.HttpServlet.service()が、HttpServlet.javaの772行目で、agency.attestation.AttestationServlet.doPost()を実行し、
agency.attestation.AttestationServlet.doPost()が、AttestationServlet.javaの161行目で、agency.attestation.AttestationServlet.doGet()を実行し、
agency.attestation.AttestationServlet.doGet()が、AttestationServlet.javaの96行目で、agency.attestation.CheckLoginInfo.doCheck()を実行した結果、
agency.attestation.CheckLoginInfo.doCheck()内のCheckLoginInfo.javaの150行目で、java.lang.NullPointerExceptionという例外が発生した
解析方法
図1のスタックトレースの解析例を、次に示します。
1行目の例外情報から、原因を特定できるかどうか確認します。
NullPointerExceptionがスローされていることがわかります。
2行目のCheckLoginInfo.javaの開発担当者であれば、150行目の実装に問題がないかどうかを確認します。
2行目のCheckLoginInfo.javaの開発担当者でない場合、スタックトレース中で最上行にある開発担当者が開発したクラスを探します。そして、そのクラスの実装に問題がないかどうかを確認します。それでも、原因を特定できない場合は、開発したクラスが使用しているクラスの提供元に調査を依頼します。
または、スタックトレースが、想定された流れでメソッドを実行しているかどうかを確認するのも1つの方法です。
図1の出力例をもとにして、解析方法を説明します。
図1の先頭の“数字:”は、説明の便宜上、付加しています。
図1 スタックトレースの出力例
1:java.util.MissingResourceException: Can't find bundle for base name sample.SampleResource, locale ja_JP 2: at java.util.ResourceBundle.throwMissingResourceException(Unknown Source) 3: at java.util.ResourceBundle.getBundleImpl(Unknown Source) 4: at java.util.ResourceBundle.getBundle(Unknown Source) 5: at sample.SampleMessage.getMessage(SampleMessage.java:15) 6: at sample.SampleServlet.doGet(SampleServlet.java:10) 7: at javax.servlet.http.HttpServlet.service(HttpServlet.java:696) 8: at javax.servlet.http.HttpServlet.service(HttpServlet.java:809) : :
解析方法
図1のスタックトレースの解析例を、次に示します。
1行目の例外情報から、原因を特定できないかを確認します。
APIリファレンスによると、java.util.MissingResourceExceptionは、Javaのリソースがない場合に発生する例外です。また、エラーメッセージによると、sample.SampleResourceというリソースファイルの日本語版(ja_JP)がないということがわかります。
リソースファイルを確認します。
リソースファイル名を誤っていないか
SampleMessage.javaの15行目のsample.SampleMessage.getMessage ()内で、java.util.ResourceBundle.getBundle()を実行した結果、例外がスローされています。したがって、そこでjava.util.ResourceBundle.getBundle()に渡しているリソースファイル名に誤りがないかどうかを確認します。
リソースファイルが、所定のディレクトリ構成内に存在するか
a)のリソースファイル名が正しい場合、所定のディレクトリ構成(/sample/)に、次のいずれかのリソースファイルがあるかどうかを確認します。
SampleResource_ja_JP.properties
SampleResource_ja_JP.class
SampleResource_ja.properties
SampleResource_ja.class
SampleResource.properties
SampleResource.class
JDK/JRE 1.4以降、java.lang.Throwableに次のコンストラクタとメソッドが追加されました。
Throwable(java.lang.String, java.lang.Throwable)
Throwable(java.lang.Throwable)
initCause(java.lang.Throwable)
これにより、スタックトレースには、原因となる例外のスタックトレースも出力されるようになりました。
以降、図1のサンプルを使って、説明します。
図1 サンプルプログラム
1 :public class Test { 2 : 3 : public static void main(String[] args) { 4 : new Test(); 5 : } 6 : 7 : Test() { 8 : try{ 9 : parentMethod(); 10: } catch (Exception e) { 11: e.printStackTrace(); 12: } 13: } 14: 15: void parentMethod() throws HiLevelException { 16: try { 17: childMethod(); 18: } catch (Exception e) { 19: throw new HiLevelException("HiLevel", e); 20: } 21: } 22: 23: void childMethod() throws LowLevelException { 24: throw new LowLevelException("LowLevel"); 25: } 26:} 27: 28:class HiLevelException extends Exception { 29: HiLevelException(String msg, Throwable cause) { 30: super(msg, cause); 31: } 32:} 33: 34:class LowLevelException extends Exception { 35: LowLevelException(String msg) { 36: super(msg); 37: } 38:}
図1のサンプルを実行すると、図2のスタックトレースが出力されます。
図2 スタックトレース
HiLevelException: HiLevel at Test.parentMethod(Test.java:19) at Test.<init>(Test.java:9) at Test.main(Test.java:4) Caused by: LowLevelException: LowLevel at Test.childMethod(Test.java:24) at Test.parentMethod(Test.java:17) ... 2 more
HiLevelExceptionに続いて、“Caused by:”以降に、原因となるLowLevelExceptionのスタックトレースが出力されています。最終行の“... 2 more”は、“Caused by:”の直前の2行が続きのスタックトレースであることを示しています。
つまり、図3のように解釈することができます。
図3 原因となる例外の解釈
Caused by: LowLevelException: LowLevel at Test.childMethod(Test.java:24) at Test.parentMethod(Test.java:17) at Test.<init>(Test.java:9) at Test.main(Test.java:4)
以上から、次のことがわかります。
スタックトレースの原因は、LowLevelExceptionである
Test.main(Test.javaの4行目)が、最初の呼び出し元である。
詳細は、Java APIリファレンスのjava.lang.ThrowableのprintStackTraceメソッドの解説を参照してください。