자바로 프로젝트를 진행할 때, 보통 에러 처리의 일관성과 가독성, 로깅, 디버깅, 예외 처리 유연성을 위해서 CustomException
클래스를 정의하여 자주 사용한다.
그러나 여러 이점들이 있음에도, 자바에서는 Exception의 처리 비용이 매우 비싸다는 문제가 있다.
이번 글에서는 JVM이 Exception을 처리하는 순서와 생성 비용이 비싼 이유, 마지막으로 비용 절감 방법에 대해서 알아보도록 하겠다.
이 글을 참고해보면, Exception이 발생하면 다음과 같이 JVM에서 Exception을 수행한다.
DefaultExceptionHandler
를 사용해 예외를 처리한다.예외가 발생한 메서드에서 바로 처리가 된다면 가장 좋지만, 바로 처리되지 못한다면 JVM은 해당 예외를 처리할 수 있는 메서드를 찾을 때 까지 계속해서 상위 메서드로 거슬러 올라가면서 메모리의 호출 스택(call stack)을 탐색하게 된다.
위에서 언급한 것 처럼 호출 스택을 탐색하는 과정도 비용이지만, fillInStackTrace()
메서드가 호출 스택을 순회하며 클래스명, 메서드명, 코드 줄 번호 등의 정보를 모아 stacktrace로 만드는 과정 또한 비용 증가의 원인이라고 볼 수 있다.
fillInStackTrace()
는 Throwable 클래스에 정의된 구현 메서드로 생성자에서 호출되도록 되어있다.
모든 Exception은 Throwable을 상속받도록 되어있기 때문에 해당 메서드를 가지고 있다.
일반적으로 stacktrace 생성하는 시간은 몇 밀리초에서 몇 초까지 다양하다. 하지만 이는 예외가 발생한 환경과 stacktrace의 depth, stackframe의 메서드 호출 수, JVM 버전 및 설정 등에 따라 다르기 때문에 시간을 특정하기는 어렵다.
그러나 확실한 것은 stacktrace가 깊을수록 오랜 시간이 걸린다는 것이다.
두 가지 방법을 소개해보겠다.
보통 NPE나 OOM와 같이 자바에서 기본적으로 제공하는 예외를 제외한, CustomException은 에러의 추족보다는 유효하지 않은 값일 때 하위 비즈니스 로직을 수행하지 못하도록 하기 위한 용도로 주로 사용된다. 따라서 보통 StackTrace가 필요가 없다.
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
때문에 단순히 try-catch로 이후 flow를 제어하거나 Spring 환경에서 @ControllerAdvice로 예외 처리하는 경우에는 불필요한 성능 저하를 막기 위해 trace를 저장하지 않도록 오버라이딩 하여 처리할 수 있다.
public class DuplicateLoginException extends RuntimeException {
public DuplicateLoginException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
static final로 선언하여 예외를 미리 캐싱해서 사용하는 것이다.
일종의 상수 값 형태로 예외를 캐싱해 두고 쓰는 것이 매번 같은 종류의 예외로 new로 생성하는 방법보다 효율적이다.
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); //생략
}
위와 같이 Exception 클래스에 예외 상황에 대한 적당한 응답 메시지나 코드를 담도록 한 뒤, 아래처럼 예외 발생 상황에서 new 키워드 없이 throw를 수행하도록 한다.
if (StringUtils.isBlank(parameter)) {
throw WebtoonCoreException.INVALID_PARAMETER;
}
일상적으로 만들어 던지던 커스텀 예외에 대해 이렇게 해볼 생각은 못해봤었는데, 좋은 인사이트 감사합니다!