1. 概要

この短い記事では、Java例外スタックトレースに不明なソースが表示される理由と、それを修正する方法について説明します。

2. クラスデバッグ情報

Javaクラスファイルには、デバッグを容易にするためのオプションのデバッグ情報が含まれています。 コンパイル時に、すべてのデバッグ情報をクラスファイルに追加するかどうかを選択できます。 これにより、実行時に使用できるデバッグ情報が決まります。

Javaコンパイラのヘルプドキュメントを調べて、利用可能なさまざまなオプションを確認しましょう。

javac -help

Usage: javac <options> <source files>
where possible options include:
  -g                         Generate all debugging info
  -g:none                    Generate no debugging info
  -g:{lines,vars,source}     Generate only some debugging info

Javaのコンパイラのデフォルトの動作は、 -g:lines、source。と同等の行とソース情報をクラスファイルに追加することです。

2.1. デバッグオプションを使用したコンパイル

上記のオプションを使用してJavaクラスをコンパイルするとどうなるか見てみましょう。 StringIndexOutOfBoundsExceptionを意図的に生成するMainクラスがあります。

使用するコンパイルメカニズムに応じて、それに応じてコンパイルオプションを指定する必要があります。 ここでは、Mavenとそのコンパイラプラグインを使用して、コンパイラオプションをカスタマイズします。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <compilerArgs>
            <arg>-g:none</arg>
        </compilerArgs>
    </configuration>
</plugin>

-gnoneに設定しました。これは、コンパイルされたクラスのデバッグ情報が生成されないことを意味します。 バグのあるMainクラスを実行すると、例外が発生した行番号ではなく、不明なソースが表示されるスタックトレースが生成されます。

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
  at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
  at java.base/java.lang.String.substring(String.java:1907)
  at com.baeldung.unknownsourcestacktrace.Main.getShortenedName(Unknown Source)
  at com.baeldung.unknownsourcestacktrace.Main.getGreetingMessage(Unknown Source)
  at com.baeldung.unknownsourcestacktrace.Main.main(Unknown Source)

生成されたクラスファイルに何が含まれているかを見てみましょう。 これを行うには、Javaクラスファイル逆アセンブラであるjavapを使用します。

javap -l -p Main.class

public class com.baeldung.unknownsourcestacktrace.Main {
    private static final org.slf4j.Logger logger;
    private static final int SHORT_NAME_LIMIT;
    public com.baeldung.unknownsourcestacktrace.Main();
    public static void main(java.lang.String[]);
    private static java.lang.String getGreetingMessage(java.lang.String);
    private static java.lang.String getShortenedName(java.lang.String);
    static {};
}

ここで期待するデバッグ情報を知るのは難しいかもしれないので、コンパイルオプションを変更して何が起こるか見てみましょう。

2.3. 修正

コンパイルオプションを-g:lines、vars、source に変更して、 LineNumberTable、 LocalVariableTable Sourceの情報をクラスファイル。 また、 -g を使用するのと同じで、すべてのデバッグ情報を入力します。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <compilerArgs>
            <arg>-g</arg>
        </compilerArgs>
    </configuration>
</plugin>

バギーメインクラスを再度実行すると、次のようになります。

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
  at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
  at java.base/java.lang.String.substring(String.java:1907)
  at com.baeldung.unknownsourcestacktrace.Main.getShortenedName(Main.java:23)
  at com.baeldung.unknownsourcestacktrace.Main.getGreetingMessage(Main.java:19)
  at com.baeldung.unknownsourcestacktrace.Main.main(Main.java:15)

出来上がり、スタックトレースに行番号情報が表示されます。 クラスファイルで何が変更されたかを見てみましょう。

javap -l -p Main

Compiled from "Main.java"
public class com.baeldung.unknownsourcestacktrace.Main {
  private static final org.slf4j.Logger logger;

  private static final int SHORT_NAME_LIMIT;

  public com.baeldung.unknownsourcestacktrace.Main();
    LineNumberTable:
      line 7: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Lcom/baeldung/unknownsourcestacktrace/Main;

  public static void main(java.lang.String[]);
    LineNumberTable:
      line 12: 0
      line 13: 8
      line 15: 14
      line 16: 29
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      30     0  args   [Ljava/lang/String;
          8      22     1  user   Lcom/baeldung/unknownsourcestacktrace/dto/User;

  private static java.lang.String getGreetingMessage(java.lang.String);
    LineNumberTable:
      line 19: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      28     0  name   Ljava/lang/String;

  private static java.lang.String getShortenedName(java.lang.String);
    LineNumberTable:
      line 23: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  name   Ljava/lang/String;

  static {};
    LineNumberTable:
      line 8: 0
}

クラスファイルには、次の3つの重要な情報が含まれています。

  1. Source .classファイルが生成された.javaファイルを示す上部ヘッダー。 スタックトレースのコンテキストでは、例外が発生したクラス名を提供します。
  2. LineNumberTable は、JVMが実際に実行するコードの行番号をソースコードファイルの行番号にマップします。 スタックトレースのコンテキストでは、例外が発生した行番号を提供します。 デバッガーでブレークポイントを使用できるようにするためにも、これが必要です。
  3. LocalVariableTable には、ローカル変数の値を取得するための詳細が含まれています。 デバッガーはこれを使用してローカル変数の値を読み取ることができます。 スタックトレースのコンテキストでは、これは重要ではありません。

3. 結論

これで、Javaコンパイラによって生成されるデバッグ情報に精通しました。 それらを操作する方法、-gコンパイラオプション。 Mavenコンパイラプラグインを使用してこれを行う方法を確認しました。

したがって、スタックトレースで不明なソースが見つかった場合は、クラスファイルを調べて、デバッグ情報が利用可能かどうかを確認できます。 その後、この問題を解決するために、ビルドツールに基づいて適切なコンパイルオプションを選択できます。

いつものように、完全なコードとMaven構成はGitHub利用できます。