예외비용 절감

213kky·2025년 2월 27일

서론

프로젝트를 진행하며 공통예외 처리를 하기 위해 GlobalExceptionHandler 클래스를 만들고 작성하면서 참고할만한 부분이 있을까 하여 인터넷을 검색하던 중 예외 생성 비용이 비싸다 라는 글을 발견하게 되었다.

사실 이전부터 예외처리가 비용이 많이 든다는 이야기를 어디선가 자주 접했었는데 이번기회에 어떤 것 때문에 예외처리 비용이 많이 든다 하는지 또 개선 방법이 있는지 적용도 해보고 작성도 해보려한다.

예외처리 비용이 많이드는 이유

jvm에서 예외를 처리하는 순서는 아래와 같다.

  1. 예외발생
  2. 예외객체 전파
  3. 예외처리
  4. 예외처리 실패
  5. Default Exception Handler 실행

예외가 발생한 메서드에서 바로 처리 가능하다면 비교적 적은 비용으로 처리 할 수 있지만, 바로 처리되지 못하면 JVM은 해당 예외를 처리할 수 있는 메서드를 찾을 때 까지 계속해서 상위 메서드로 거슬러 올라가면서 메모리의 호출 스택을 탐색한다.

호출 스택을 탐색하는 과정도 비용이 들고, fillInStackTrace() 메서드가 호출 스택을 순회하며 클래스명, 메서드명, 코드 줄 번호 등의 정보를 모아 stacktrace로 만드는 과정 또한 비용 증가의 원인이다.

단순히 생각해봐도 정보를 모으는 양이 늘어나기 때문에 스택 트레이스가 깊을수록 비용이 증가하는것은 당연해보인다.

fillInStackTrace()는 Throwable 클래스에 정의된 구현 메서드로 생성자에서 호출되도록 되어있며, 모든 Exception은 Throwable을 상속받도록 되어있기 때문에 fillInStackTrace() 메서드를 가지고 있다.

스택 트레이스를 생성하는 시간은 몇 밀리초에서 몇 초까지 다양하다. 이는 예외가 발생한 환경, 스택 트레이스의 깊이, StackFrame의 메서드 호출 수, JVM의 버전 및 설정 등에 다르다.

비용을 줄이는 방법

1. fillInStackTrace() 메서드 재정의

프로젝트에서는 보통 정의된 예외 이외에 커스텀 예외를 발생시키는 경우가 많은데 이런 커스텀예외는 에러의 추적보다는 이후 비즈니스 로직을 실행하지 못하게 하는 용도이기 때문에 스택트레이스가 필요 없다.

아래는 내가 작성했던 커스텀 예외를 위한 클래스이다.
간단하게 메시지와 상태코드만을 입력하도록 작성되어있다.

@Getter
public class BaseException extends RuntimeException {

    private final int code;

    public BaseException(int code, String message) {
        super(message);
        this.code = code;
    }
}

단순히 Try-Catch로 이후의 로직를 제어하거나, Spring 환경에서 @ControllerAdvice로 예외를 처리하는 경우에는 스택 트레이스가 필요가 없기 때문에 불필요한 비용 낭비를 막기 위해 아무 trace도 저장하지 않도록 아래 코드처럼 fillInStackTrace()를 오버라이딩하여 처리하였다.

@Getter
public class BaseException extends RuntimeException {

    private final int code;

    public BaseException(int code, String message) {
        super(message);
        this.code = code;
    }

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
}

fillInStackTrace() 재정의 전

fillInStackTrace() 재정의 후


2. 예외 캐싱

예시

public class CustomException extends RuntimeException {
    public static final CustomException INVALID_NICKNAME = new CustomException(ResponseType.INVALID_NICKNAME);
    public static final CustomException INVALID_PARAMETER = new CustomException(ResponseType.INVALID_PARAMETER);
    public static final CustomException INVALID_TOKEN = new CustomException(ResponseType.INVALID_TOKEN);
    
}


코드에 적용 시..
if (StringUtils.isBlank(parameter)) {
	throw WebtoonCoreException.INVALID_PARAMETER;
}

static final로 선언하여 이용해 예외를 미리 캐싱하여 사용할 수 있다고 한다.
상수처럼 미리 작성해 놓고 new를 사용하지 않고 같은 종류의 예외를 사용하는 방식이다.
확실히 매번 new 로 객체를 생성하는 것 보다 효율적인 것 같다.

참고
https://dkswnkk.tistory.com/692

profile
since 2022

0개의 댓글