[JAVA] JVM의 명령행 플래그에 대해 모르고 있던 5가지 사항 Enjoy/JAVA2012. 3. 9. 15:38
JVM의 명령행 플래그에 대해 모르고 있던 5가지 사항
JVM 성능 및 Java 런타임 정밀 조정하기
JVM은 Java 애플리케이션의 기능과 성능을 뒷받침하는 일꾼이지만 대부분의 Java 개발자는 이를 당연한 것으로 여기고 중요하게 생각하지 않는다. 그리고 오브젝트 할당 및 가비지 콜렉션, 스레드 실행, 파일 열기 및 닫기, Java 바이트코드 해석 및/또는 JIT 컴파일 등의 작업이 JVM에서 처리되는 방법을 제대로 이해하고 있는 개발자는 거의 없다.
애플리케이션 성능에 영향을 주는 JVM 기능에 익숙하지 않을 때뿐만 아니라 JVM에서 오류가 발생한 경우에도 문제점을 해결하기가 매우 어려울 수 있다.
5가지 사항 시리즈의 이 기사에서는 Java 가상 머신의 성능을 진단 및 조정하는 데 사용할 수 있는 여러 가지 명령행 Java 플래그를 소개한다.
필자는 애플리케이션 성능 문제점에 대한 상담 의뢰를 받고 코드 전체에 대해 빠른 grep
을 수행하여 원래 Java 성능 안티 패턴인 Listing 1과 같은 내용을 발견한 경험이 얼마나 많은지 모른다.
Listing 1. System.gc();
// We just released a bunch of objects, so tell the stupid // garbage collector to collect them already! System.gc(); |
명시적 가비지 콜렉션은 정말로 나쁜 아이디어이다. 이는 마치 공중전화 부스 속에 자기 자신을 광견병에 걸린 개와 함께 가둬놓는 것과 비슷하다. 구현에 따라 호출의 정확한 의미가 달라지기는 하지만 JVM이 제너레이셔널(generational) 가비지 콜렉터(대부분의 가비지 콜렉터가 제너레이셔널 가비지 콜렉터임)를 실행하고 있을 경우 System.gc();
를 실행하면 전체 삭제가 필요하지 않더라도 VM이 힙을 "전체 삭제"하게 된다. 일반적으로 전체 삭제에는 일반적인 GC 조작보다 몇 배나 더 높은 비용이 소요된다. 이는 분명 어리석은 방법이다.
하지만 이 말을 그대로 받아들이면 안 된다. 왜냐하면 Sun의 엔지니어가 이 특별한 인적 오류 문제점을 해결하기 위한 JVM 플래그를 제공했기 때문이다. -XX:+DisableExplicitGC
플래그는 자동으로 System.gc()
호출을 무작동으로 전환한다. 따라서 코드를 실행한 후System.gc()
가 JVM의 전체 실행에 도움이 되는지 아니면 해가 되는지 여부를 직접 확인할 수 있다.
근래에 OutOfMemoryError
가 발생하면서 JVM이 종료될 뿐만 아니라 디버거를 사용하여 오류를 찾고 문제점을 확인할 수도 없는 상황을 경험한 적이 있는가? 원인을 알 수 있는 이와 같은 문제점이 발생하면 개발자가 화가 나서 정신을 차리기 어려울 것이다.
JVM이 종료되려는 순간에 힙의 스냅샷을 작성하고 싶은 경우가 있다. 바로 이 기능을 -XX:+HeapDumpOnOutOfMemoryError
명령이 수행한다.
이 명령을 실행하면 JVM이 "힙 덤프 스냅샷"을 작성한 후 처리를 위해 파일에 저장한다. 일반적으로 이 작업에는 jhat
유틸리티(필자의 이전 기사 참조)가 사용된다. 해당 -XX:HeapDumpPath
플래그를 사용하여 파일을 저장할 실제 경로를 지정할 수 있다. (파일이 어느 위치에 저장되던지 상관 없이 파일 시스템 및/또는 Java 프로세스에 해당 위치에 작성할 수 있는 권한이 있는지 확인한다.)
설치된 JRE에 포함되어 있는 경로 또는 JRE를 조금 확장한 경로와는 약간 다른 클래스 경로에 클래스를 주기적으로 저장해 두는 것이 좋다. (새 Java Crypto API 제공자가 예가 될 수 있을 것이다.) JRE를 확장하려고 하면 사용자 정의 구현을 부트스트랩 ClassLoader
에서 사용할 수 있어야 하며, 이 클래스 로더는 java.lang.Object
및 rt.jar
의 모든 항목을 로드한다.
크랙을 사용하여 rt.jar
을 열고 사용자 정의 구현 또는 새 패키지를 넣을 수도 있겠지만 이는 JDK를 다운로드할 때 동의했던 라이센스를 기술적으로 위반하는 것이다.
대신 JVM 자체의 -Xbootclasspath
옵션을 -Xbootclasspath/p
및 -Xbootclasspath/a
와 함께 사용한다.
-Xbootclasspath
를 사용하면 전체 부트 클래스 경로를 설정할 수 있으며, 이 클래스 경로는 일반적으로 rt.jar
에 대한 참조를 포함해야 하며 rt.jar
에 포함되지 않은 JDK의 다른 JAR 파일 세트도 포함해야 한다. -Xbootclasspath/p
는 값을 기존 부트 클래스 경로 앞에 추가하고, -Xbootclasspath/a
는 값을 뒤에 추가한다.
예를 들어, 설치된 java.lang.Integer
를 수정한 후 수정 사항을 서브디렉토리 mods
에 저장한 경우 -Xbootclasspath/a mods
매개변수는 새 Integer
를 기본값 앞에 놓는다.
-verbose
는 거의 모든 유형의 Java 애플리케이션에 사용할 수 있는 유용한 1차 진단 유틸리티이다. 이 플래그에는 gc
, class
및 jni
라는 세 개의 서브플래그가 있다.
gc
는 작동 중인 JVM 가비지 콜렉터로 인해 성능이 저하되었는지 확인할 때 가장 먼저 사용되는 도구이다. 아쉽게도 gc
의 출력에 대한 해석은 한 권의 책으로 다루어질 정도로 까다롭다. 게다가 명령행에 인쇄되는 출력이 Java 릴리스 또는 JVM별로 다를 수 있기 때문에 올바르게 해석하기가 더욱 어렵다.
일반적으로 말해서, 가비지 콜렉터가 제너레이셔널 콜렉터(대부분의 "엔터프라이즈급" VM)이면 전체 삭제 GC 패스를 나타내는 일종의 시각적 플래그가 표시된다. Sun JVM의 경우에는 플래그가 GC 출력 행의 맨 앞에 "[Full GC ...]
"로 표시된다.
class
는 ClassLoader
및/또는 일치하지 않는 클래스 충돌을 진단하는 데 도움이 되는 도구이다. 이 플래그는 클래스의 로드 시간뿐만 아니라 JAR 파일에 대한 경로를 포함한 클래스의 원본 위치까지도 보고한다(JAR에서 로드된 것으로 가정).
jni
는 JNI 및 네이티브 라이브러리 작업 외에는 거의 사용되지 않는다. 이 플래그를 사용하면 네이티브 라이브러리가 로드되었거나 메소드가 바인드되었다는 등의 다양한 JNI 이벤트가 보고된다. 다시 한번 말하지만 JVM의 릴리스별로 출력이 달라질 수 있다.
지금까지 JVM에서 제공하는 명령행 옵션 중에서 필자가 좋아하는 몇 가지 옵션을 살펴보았다. 하지만 직접 찾아볼 수 있는 더 많은 옵션이 있다. 명령행 인수 -X
는 JVM에서 제공하는 모든 비표준(하지만 대부분 안전한) 인수를 나열한다. 예를 들어, 다음과 같다.
-Xint
는 JVM을 해석된 모드로 실행한다. (이 인수는 JIT 컴파일러가 코드에 실제로 영향을 주는지 테스트하거나 JIT 컴파일러에 버그가 있는지 확인할 때 유용하다.)-Xloggc:
는-verbose:gc
와 동일한 기능을 제공하지만 명령행 창에 표시하는 대신 파일에 로깅한다.
JVM 명령행 옵션은 때때로 변경되므로 주기적으로 확인하는 것이 좋다. 모니터를 보고 있던 늦은 밤 중이나, 가족과 함께 저녁 만찬을 하기 위해 오후 5시에 퇴근하는 중이나 아니면 Mass Effect 2에서 적을 무찌르는 동안에 변경될 수도 있다.
명령행 플래그는 프로덕션 환경에서 지속적으로 사용하기 위한 것이 아니다. JVM 가비지 콜렉터를 조정하기 위해 사용하는 플래그를 제외하고, 비표준 명령행 플래그는 실제로 프로덕션용이 아니다. 하지만 제대로 알기 힘든 가상 머신의 내부 작업을 살펴볼 수 있는 도구라는 점에서 이러한 태그는 매우 유용하다.
5가지 사항 시리즈의 다음 주제는 일상적인 Java 도구이다.