2014-06-12

[java] shutdown hook 사용하기

 프로그램을 작성하다 보면, 프로세스가 종료될 때 반드시 실행해야 하는 코드가 나온다.
 exit 포인트가 하나뿐인 프로그램이라면, exit 하기 전에 실행하면 되지만, 보통 코드를 그렇게 작성하지 않기 때문에 Java에서는 Runtime의 shutdown hook을 이용한다.

 사용하는 방법은 간단하다. Runtime의 addShutdownHook을 이용해 필요한 hook을 추가하면 된다.
 process가 종료되기 시작하면 프로세스 내의 non-daemon thread들이 종료되기 시작한다. 모든 non-daemon thread들이 종료되면, 등록된 hook들이 실행된다.

 shutdown hook은 C나 C++의 atexit과 비슷한 역할을 하지만, 함수를 등록하는 것이 아닌 Thread를 등록한다는 것이 다르다.
 또한, atexit은 등록된 함수가 stack에 쌓여서 LIFO로 동작한다. 하지만 Java의 addShutdownHook은 등록된 Thread가 순서 없이 실행되기 시작하여 병렬적으로 돌아간다.
 hook들이 병렬적으로 돌아가기 때문에 hook의 순서를 보장하고 싶다면 같은 thread에서 실행되도록 hook을 작성해야 한다.
 물론, 일반적인 병렬 프로그래밍처럼 lock을 이용하여 순서를 보장할 수도 있다. 하지만, shutdown hook은 프로세스가 종료될 때 반드시 실행되고, hook이 종료될 때까지 정상적으로는 프로세스가 종료되지 않기 때문에, shutdown hook에서 dead lock이 발생하면 프로세스가 종료되지 않는다. 이러한 이유로 shutdown hook에서는 가능하면 lock을 사용하지 않고 로직을 작성하는 것을 권장한다.

 removeShutdownHook이라는 API도 존재한다. 이 API를 이용하여 더는 필요 없어진 hook을 제거할 수 있다. atexit을 사용할 때는 필요 없어진 hook을 제거하기 위해서는 전역 flag를 두어 flag를 설정하거나, custom한 stack을 구현해야 하는데 이럴 필요가 없어진 것이다.

 shutdown hook은 Thread이기 때문에 hook을 달아놓으면 종료될 때까지 idle한 Thread가 하나 생기는 것이므로 이것이 overhead가 되지 않을까 걱정하는 사람도 있다. 기본적으로 이것이 틀린 생각은 아니다. 하지만 대부분의 구현체에서 Thread는 실제로 시작하기 전까지는 thread stack을 할당하지 않기 때문에 걱정할 필요 없다. 물론 아주 약간의 overhead도 감당할 수 없는 embedded 환경에서는 이런 부분도 고려해야 하지만, desktop application이나 server 같은 환경에서 고려해야 할 문제는 아니다.

 shutdown hook을 사용할 때 조심해야 할 것이 2가지 있다.

 우선 shutdown hook은 최대한 짧게 작성되어야 한다.
 만약 프로세스의 종료가 머신의 종료에 의한 것이라면 JVM은 프로세스가 완전히 종료되는 것을 기다리지 않고, 일정 시간이 지난 후 종료한다. 이 때문에 shutdown hook이 실행되기 전에 종료될 수 도 있고, shutdown hook이 실행되는 도중에 종료될 수도 있다.

 두 번째로 shutdown hook을 사용하는데 조심해야 하는 것은 shutdown hook은 반드시 실행되는 것이 아니라는 것이다.
 shutdown hook은 정상적인 종료에서만 호출된다는 것이다.
 따라서 비정상적인 종료에서도 반드시 수행돼야 하는 일은 하나의 Java application만으로는 처리할 수 없다. 이런 것은 보통 Java application을 실행하는 스크립트를 작성하거나, Java application을 실행하는 application을 작성하여 처리한다.

 여기서 비정상인 종료라는 것은 에러코드를 반환하는 것이 아니다.
 System.exit으로 호출되는 경우 status 코드를 무엇으로 반환하든지 정상 종료이다.
 SIGTERM을 받아서 종료되는 경우에도 정상종료이다.
 SIGTERM을 받은 프로세스는 정상적으로 shutdown hook을 호출하고 Thread를 정리하고 죽는다.
 handle되지 않은 Exception이 발생하여 process가 종료되는 경우도 Java는 정상종료로 취급한다.

 위의 3가지 경우에는 문제없이 shutdown hook이 호출된다.

 그렇다면 비정상종료는 무엇일까?
 우선 대표적인 비정상 종료는 halt다.
 halt는 프로세스를 강제종료하는 것으로 실행되는 즉시 종료되므로 shutdown hook이 불리지 않는다.
 프로세스가 SIGKILL을 받는 경우도 비정상 종료이다. SIGTERM과는 달리 SIGKILL을 받으면 리소스를 해제하거나 shutdown hook이 불리지 않고 바로 종료된다.
 마지막으로 JVM에 문제가 발생해서 죽는 일도 있다.
 JVM에 문제가 발생하는 것은 매우 드문 일이다. 하지만 JVM 코드에 버그가 있어서 죽거나, JNI를 사용하다가 문제가 생기는 경우, shutdown hook이 호출되지 않는다.

댓글 1개:

  1. 프로세스를 실행한 후 콘솔창을 그냥 꺼버렸을 시 이것도 정상종료인가요..?
    shutdownhook이 실행되는건지 궁금합니다.. 콘솔창을 꺼버리기 때문에 로그 찍어볼 수 도 없구...

    답글삭제