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>

우리는 컴파일된 클래스에 대해 디버깅 정보가 생성되지 않음을 의미하는 없음 으로 -g 를 설정했습니다. 버그 가 있는 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,  LocalVariableTableSource 정보를 클래스 파일에 넣겠습니다. 또한 모든 디버그 정보를 입력하는 -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>

버그 가 있는 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(Main.java:23)
  at com.baeldung.unknownsourcestacktrace.Main.getGreetingMessage(Main.java:19)
  at com.baeldung.unknownsourcestacktrace.Main.main(Main.java:15)

Voila, 스택 추적에서 줄 번호 정보를 볼 수 있습니다. 클래스 파일에서 변경된 사항을 살펴보겠습니다.

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
}

우리의 클래스 파일에는 이제 세 가지 중요한 정보가 포함되어 있습니다.

  1. Source , .class 파일이 생성된 .java 파일을 나타내는 최상위 헤더 입니다. 스택 추적 컨텍스트에서 예외가 발생한 클래스 이름을 제공합니다.
  2. LineNumberTable 은 JVM이 실제로 실행하는 코드의 줄 번호를 소스 코드 파일의 줄 번호에 매핑합니다. 스택 추적 컨텍스트에서 예외가 발생한 줄 번호를 제공합니다. 디버거에서 중단점을 사용하려면 이것이 필요합니다.
  3. LocalVariableTable 에는 로컬 변수의 값을 가져오는 세부 정보가 포함되어 있습니다. 디버거는 이를 사용하여 로컬 변수의 값을 읽을 수 있습니다. 스택 추적의 맥락에서 이것은 중요하지 않습니다.

3. 결론

이제 Java 컴파일러에서 생성된 디버그 정보에 익숙해졌습니다. 그것들을 조작하는 방법, -g 컴파일러 옵션. 우리는 Maven 컴파일러 플러그인으로 어떻게 그렇게 할 수 있는지 보았습니다.

따라서 스택 추적에서 알 수 없는 소스를 찾으면 클래스 파일을 조사하여 디버그 정보를 사용할 수 있는지 여부를 확인할 수 있습니다. 그런 다음 이 문제를 해결하기 위해 빌드 도구를 기반으로 올바른 컴파일 옵션을 선택할 수 있습니다.

항상 그렇듯이 전체 코드와 Maven 구성은 GitHub에서 사용할 수 있습니다 .

Generic footer banner