포스트

2021-04-02-TIL

2021-04-02-TIL

finally에 안 올 수 있는 상황이 어떤게 있을까?

JLS는 finally 블록이 try와 catch가 어떤 방식으로 끝나든 그 뒤에 실행된다고 정의한다(JLS 14.20.2). return, break, 예외 모두 finally를 거친 뒤에 제어가 넘어간다. 그런데 이 보장은 “JVM이 계속 실행 중일 때”라는 전제 위에 있다. 그래서 finally에 오지 못하는 경우는 대부분 JVM이나 스레드가 그 지점까지 가지 못하는 경우다.

  • System.exit()를 호출한 경우. Runtime.exit는 JVM 종료 절차를 시작하고 정상적으로 반환되지 않는다. 종료 훅(shutdown hook)은 돌지만 try 안의 finally는 돌지 않는다.
  • Runtime.halt()로 강제 종료한 경우. 이때는 종료 훅도 실행되지 않는다.
  • 데몬 스레드 안에서 try를 실행하던 중에 데몬이 아닌 스레드가 모두 끝난 경우. Thread 문서대로 JVM은 데몬이 아닌 스레드가 모두 죽으면 종료하므로, 데몬 스레드의 finally는 실행되지 않을 수 있다.
  • try 안에서 무한 루프를 돌거나 영원히 블록되는 경우, 그리고 프로세스가 OS에 의해 강제로 죽는 경우.

반대로 finally가 너무 잘 실행되어서 생기는 문제도 있다. finally에서 return하거나 예외를 던지면, try에서 진행 중이던 return 값이나 예외가 버려진다. JLS도 finally의 갑작스러운 종료(abrupt completion)가 앞서 시작된 제어 이동을 덮어쓸 수 있다고 설명한다. 그래서 finally에는 자원 정리만 두고 return은 두지 않는 편이 안전하다.


바이트 코드로 본 finally

바이트 코드로 확인하면 finally가 try와 catch에 모두 덮어쓴다.

예전 컴파일러는 jsr/ret 명령으로 finally 블록을 서브루틴처럼 호출했다(JVMS 3.13). 클래스 파일 버전 51.0(Java 7) 이상에서는 jsr와 ret가 아예 나올 수 없다(JVMS 4.9.1). 그래서 지금의 javac는 finally 코드를 try의 정상 종료 경로와 각 catch 경로, 예외 핸들러 경로에 복사해 넣는다. javap -c로 클래스 파일을 열어 보면 같은 명령어 묶음이 여러 번 반복되는 것이 그 흔적이다.


예외는 사용자 입장에서 읽혀야 한다

프론트엔드에 넘겨줄 때는 무조건 api이다. (JSON 포맷)

사용자 입장에서 적절히 알아볼 수 있는 예외를 작성하는 것이 중요하다!

서버 내부에서는 스택 트레이스와 예외 클래스 이름이 디버깅 정보지만, API를 호출하는 쪽에는 의미가 없다. 그래서 예외를 잡아서 클라이언트가 이해할 수 있는 상태 코드와 메시지를 담은 JSON으로 바꿔 보내야 한다. 예외 클래스를 도메인 상황별로 나누어 두면 이 변환을 한곳에서 처리하기 쉽다.

roach의 깃허브 및 velog 참고

  • https://github.com/tmdgusya/ExceptionSampleCode
  • https://velog.io/@tmdgusya/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC
참고한 자료외부 출처 4

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다