티스토리 뷰
목차
자바 가상 머신(JVM), 강제종료 원인, 종료 훅 문제
자바 가상 머신 JVM이 종료되는 두 가지 경우를 생각할 수 있는데, 하나는 예정된 절차대로 종료되는 경우이고, 또 하나는 예기치 못하게 임의로 종료되는 경우이다.
절차에 맞춰 종료되는 경우에는 '일반'(데몬이 아닌) 스레드가 모두 종료되는 시점, 또는 어디에선가 System.exit 메소드를 호출하거나 기타 여러 가지 상황(예를 들면 SIGINT 시그널을 받거나 CTRL+C 키를 입력한 경우)에 자바 가상 머신 JVM 종료 절차가 시작된다.
이런 방법이 자바 가상 머신 JVM을 종료하는 가장 적절한 방법이며, 그 외에 Runtime.halt 메소드를 호출하거나 운영체제 수준에서 JVM 프로세스를 강제로 종료하는 방법(예를 들어 SIGKILL 시그널을 보내는 경우) 등으로 종료시킬 수도 있다.
종료 훅 문제
예정된 절차대로 종료되는 경우에 자바 가상 머신 JVM은 가장 먼저 등록되어 있는 모든 종료 훅shutdown hook을 실행시킨다. 종료 훅은 Runtime.addShutdownHook 메소드를 사용해 등록되지 않아 아직 시작되지 않은 스레드를 의미한다.
하나의 자바 가상 머신 JVM에 여러 개의 종료 훅을 등록할 수도 있으며, 두 개 이상의 종료 훅이 등록되어 있는 경우에 어떤 순서로 훅을 실행하는지는 아무런 규칙이 없다.
[자바 가상 머신(JVM), 강제종료 원인, 종료 훅 문제]
JVM 종료 절차가 시작됐는데 (데몬이건 데몬이 아니건 간에) 애플리케이션에서 사용하던 스레드가 계속해서 동작 중이라면 종료 절차가 진행되는 과정 내내 기존의 스레드도 계속해서 실행되기도 한다.
종료 훅이 모두 작업을 마치고 나면 자바 가상 머신 JVM은 runFinalizersOnExit 값을 확인해 true라고 설정되어 있으면 클래스의 finalize 메소드를 모두 호출하고 종료한다.
JVM은 종료 과정에서 계속해서 실행되고 있는 애플리케이션 내부의 스레드에 대해 중단 절차를 진행하거나 인터럽트를 걸지 않는다. 계속해서 실행되던 스레드는 결국 종료 절차가 끝나는 시점에 강제로 종료된다.
만약 종료 훅이나 finalize 메소드가 작업을 마치지 못하고 계속해서 실행된다면 종료 절차가 멈추는 셈이며, JVM은 계속해서 대기 상태로 머무르기 때문에 결국 자바 가상 머신 JVM을 강제로 종료하는 수밖에 없다.
[자바 가상 머신(JVM), 강제종료 원인, 종료 훅 문제]
JVM을 강제로 종료시킬 때는 JVM이 스스로 종료되는 것 이외에 종료 훅을 실행하는 등의 어떤 작업도 하지 않는다.
따라서 종료 훅은 스레드 안전하게 만들어야만 한다. 공유된 자료를 사용해야 하는 경우에는 반드시 적절한 동기화 기법을 적용해야 한다.
이에 더해 애플리케이션의 상태에 대해 어떤 가정(예를 들어 애플리케이션에서 사용한 서비스는 이미 종료됐을 것이고, 일반 스레드는 모두 종료됐을 것이라는 가정)도 해서는 안 되며, 자바 가상 머신 JVM이 종료되는 원인에 대해서도 생각해서는 안 되는 등 어떤 상황에서도 아무런 가정 없이 올바로 동작할 수 있도록 굉장히 방어적인 형태로 만들어야 한다.
마지막으로 JVM이 종료될 때 종료 훅의 작업이 끝나기를 기다리기 때문에 마무리 작업을 최대한 빨리 끝내고 바로 종료돼야 한다. 종료 훅이 실행되는 시간이 오래 걸린다면, 사용자는 애플리케이션이 종료될 때까지 한참을 기다려야 하므로 역시 프로그램의 응답 속도를 늦추는 결과가 된다.
종료 훅은 어떤 서브신이나 애플리케이션 자체의 여러 부분을 정리하는 목적으로 사용하기 좋다. 예를 들어 임시로 만들어 사용했던 파일을 삭제하거나, 운영체제에서 알아서 정리해주지 않는 모든 자원을 종료 훅에서 정리해야 한다.
종료 훅이 여러 개 등록된 경우에는 여러 개의 종료 훅이 서로 동시에 실행되기 때문에 다른 종료 훅에서 해당 LogService를 사용하고 있었다면 로그를 남기고자 할 때 이미 LogService가 종료되어 문제가 발생할 수 있다.
자바 가상 머신의 이런 경우를 예방하려면 종료 훅에서는 애플리케이션이 종료되거나 다른 종료 훅이 종료시킬 수 있는 서비스는 사용하지 말아야 한다.
이런 문제를 쉽게 해결하려면 서비스별로 각자 종료 훅을 만들어 등록하기보다는 모든 서비스를 정리할 수 있는 하나의 종료 훅을 사용해 각 서비스를 의존성에 맞춰 순서대로 정리하는 것도 방법이다.
이런 방법으로 각 서비스를 차례대로 정리하도록 하면 종료 훅의 작업이 단일 스레드에서 차례로 일어나기 때문에 종료 훅 간에 혹시나 발생할 수 있는 경쟁 조건이나 데드락 등의 상황을 미리 방지할 수 있다.
[자바 가상 머신(JVM), 강제종료 원인, 종료 훅 문제]
이와 같은 기법은 종료 훅을 사용하건 사용하지 않건 언제든지 적용할 수 있으며, 어떤 방법을 사용하건 종료할 때 마무리 절차를 여러 개의 스레드를 사용해 동시에 처리하는 것보다는 순차적인 방법으로 차례대로 처리하면 자바 가상 머신의 문제점이 발생하는 경우를 줄일 수 있다.
실제로 서비스 간의 종속성이 명확히 눈에 보이는 애플리케이션의 경우, 종료 시점의 마무리 절차를 차례로 처리하도록 하면 올바른 순서대로 서비스를 종료하고 마무리할 수 있다.
예제. 로그 서비스를 종료하는 종료 훅을 등록
1 2 3 4 5 6 7 8 | public void start() { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { LogService.this.stop(); } catch (InterruptedException ignored) {} } }); } | cs |
출처 - 자바 병렬 프로그래밍, 강철구, 245p~249p
자바 가상 머신(JVM), 강제종료 원인, 종료 훅 문제