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
댓글
아직 댓글이 없습니다