Technical Note/SERVER PERFORMANCE

JConsole 및 VisualVM과 같이 전체 기능을 갖춘 내장 프로파일러는 성능 오버헤드에 비해 많은 비용이 드는 경우도 있으며, 특히 프로덕션 하드웨어에서 실행되는 시스템의 경우 그러한 현상이 두드러진다. 따라서 Java 성능 모니터링을 중점적으로 다루는 이 두 번째 기사에서는 개발자가 실행 중인 Java 프로세스의 한 특성에만 집중할 수 있도록 도와 주는 다섯 가지 명령행 프로파일링 도구를 소개한다.

JDK에는 Java 애플리케이션 성능을 모니터링 및 관리하는 데 사용할 수 있는 여러 가지 명령행 유틸리티가 있다. 이러한 유틸리티의 대부분은 실험 수준의 유틸리티이기 때문에 기술적으로 지원되지는 않지만 그럼에도 불구하고 여전히 유용하며, 일부 유틸리티는 JVMTI 또는 JDI를 사용하여 빌드할 수 있는 특수 용도의 도구를 개발하는 데도 사용될 수 있다(참고자료 참조).

1. jps(sun.tools.jps)

많은 명령행 도구에서는 사용자가 모니터링하려는 Java 프로세스를 식별해야 한다. 이는 원시 운영 체제 프로세스를 모니터링하면서 작업할 프로세스 ID를 요구하는 비슷한 도구와 별로 다르지 않다.

JDK의 jps 유틸리티가 필요한 이유는 "VMID" ID가 원시 운영 체제 프로세스 ID("pid")와 항상 같지는 않기 때문이다.

Java 프로세스 내에서 jps 사용하기

JDK에 포함된 대부분의 도구뿐만 아니라 이 기사에서 언급하는 모든 도구와 마찬가지로 jps 실행 파일은 대부분의 작업을 수행하는 Java 클래스 또는 클래스 세트의 씬 랩퍼이다. Windows®의 경우 이러한 도구는 JNI Invocation API를 사용하여 대상 클래스를 직접 호출하는 .exe 파일이다. UNIX®의 경우에는 대부분의 도구가 지정된 클래스 이름을 사용하여 제네릭 Java 실행 프로그램을 실행하는 쉘 스크립트에 대한 기호 링크이다.

Ant 스크립트와 같은 Java 프로세스 내에서 jps(또는 기타 도구)의 기능을 사용하려는 경우에는 비교적 쉽게 각 도구의 "main" 클래스인 클래스에 있는 main()을 호출하면 된다. 쉽게 참조할 수 있도록 해당 클래스 이름이 각 도구의 이름 뒤에 있는 괄호 안에 표시된다.

대부분의 UNIX 시스템에 있는 ps 유틸리티를 연상시키는 jps는 실행 중인 Java 애플리케이션의 JVMID를 알려 준다. 이름으로 알 수 있듯이jps는 지정된 시스템에서 실행 중인 검색 가능한 모든 Java 프로세스의 VMID를 리턴한다. jps에 프로세스가 검색되지 않는다는 것이 해당 Java 프로세스를 연결 또는 탐색할 수 없음을 의미하지는 않는다. 단지 프로세스가 자신을 사용할 수 있는 자원으로 표시하지 않고 있다는 것을 의미한다.

Java 프로세스를 검색할 수 있는 경우에는 jps가 해당 프로세스를 실행하는 데 사용되는 명령행을 나열한다. 운영 체제와 관련하여 모든 Java 프로그램은 "java"이기 때문에 Java 프로세스를 구별하는 이 방법이 중요하다. 대부분의 경우 VMID가 중요한 번호이므로 기록해 두는 것이 좋다.

프로파일러 시작하기

프로파일링 유틸리티를 가장 쉽게 시작하는 방법은demo/jfc/SwingSet2에 있는 SwingSet2 데모와 같은 데모 프로그램을 사용하는 것이다. 이렇게 하면 백그라운드/디먼 프로세스로 실행 중인 프로세스와의 충돌 가능성을 없앨 수 있다. 도구와 그 오버헤드에 익숙해진 후에는 실제 프로그램에서 사용해 볼 수 있다.

데모 애플리케이션을 로드한 후 jps를 실행하고 리턴된 vmid를 기록해 둔다. 최상의 효과를 얻기 위해 -Dcom.sun.management.jmxremote 특성 설정을 사용하여 Java 프로그램을 실행한다. 이 설정을 사용하지 않을 경우에는 이후에 사용하는 일부 도구에 의해 수집된 일부 데이터를 사용할 수 없다.

위로

2. jstat(sun.tools.jstat)

jstat 유틸리티는 다양한 통계를 수집하는 데 사용할 수 있다. jstat 통계는 명령행에서 첫 번째 매개변수로 지정하는 "options"에 저장된다. JDK 1.6의 경우 jstat를 -options 명령과 함께 실행하여 사용할 수 있는 옵션 목록을 볼 수 있다. Listing 1에서 일부 옵션을 보여 준다.


Listing 1. jstat 옵션

-class
-compiler
-gc
-gccapacity
-gccause
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcpermcapacity
-gcutil
-printcompilation 


이 유틸리티의 JDK 문서(참고자료 참조)를 보면 Listing 1의 각 옵션이 리턴하는 결과를 볼 수 있다. 하지만 이러한 옵션의 대부분은 가비지 콜렉터 또는 그 일부에 대한 성능 정보를 수집하는 데 사용된다. -class 옵션은 로드된 클래스와 로드되지 않은 클래스를 보여 주며(따라서 애플리케이션 서버 또는 코드 내에서 발생한 ClassLoader 누수를 발견할 수 있는 뛰어난 유틸리티로 활용할 수 있음) Hotspot JIT 컴파일러의 -compiler 및 -printcompilation 표시 정보도 보여 준다.

기본적으로 jstat는 사용자가 검사한 시점의 정보를 표시한다. 정기적으로 스냅샷을 작성하려면 -options 명령 뒤에 밀리초 단위로 간격을 지정한다. 그러면 jstat가 모니터링된 프로세스의 정보에 대한 스냅샷을 지속적으로 표시한다. 종료 전에 jstat로 특정 개수의 스냅샷을 작성하려면 간격/기간 값 뒤에 해당 개수를 지정한다.

몇 분 전에 시작된 실행 중인 SwingSet2 프로세스의 VMID가 5756이라고 가정할 경우 다음 명령은 jstat에게 10회를 반복해서 250밀리초마다 gc 스냅샷 덤프를 작성한 다음 종료하도록 지시한다.

jstat -gc 5756 250 10


다양한 옵션의 출력 또는 옵션 자체를 경고 없이 변경할 수 있는 권한은 Sun(현재는 Oracle)에 있다. 이는 지원되지 않은 유틸리티를 사용하는 데 단점이다. jstat 출력의 각 열에 대한 자세한 정보를 보려면 Javadocs를 참조한다.

위로

3. jstack(sun.tools.jstack)

일반적인 진단 작업에서는 실행 중인 스레드와 관련하여 Java 프로세스의 내부에서 어떤 작업이 수행되는지를 아는 것이 중요하다. 예를 들어, 애플리케이션이 갑자기 중지된 경우 일종의 오류가 발생했다는 것은 분명하지만 코드를 보면서 오류가 발생한 위치와 그 이유를 알기는 쉽지 않다.

jstack는 애플리케이션에서 실행 중인 다양한 스레드에 대한 전체 덤프를 리턴하는 유틸리티이므로 이 유틸리티를 사용하여 문제점을 찾아낼 수 있다.

원하는 프로세스의 VMID를 사용하여 jstack을 실행하면 스택 덤프가 생성된다. 이 경우 jstack은 Java 프로그램이 실행 중인 콘솔 창에서 Ctrl-Break를 누르거나 VM 내에서 각 Thread 오브젝트의 Thread.getAllStackTraces() 또는 Thread.dumpStack()을 호출하는 것과 동일하게 동작한다. 또한 jstack 호출은 Thread 오브젝트로 사용하지 못할 수도 있는 VM 내에서 실행 중인 비Java 스레드에 대한 덤프 정보도 제공한다.

jstack의 -l 매개변수는 각 Java 스레드에 의해 설정된 잠금에 대한 세부 정보가 포함된 약간 더 긴 덤프를 제공하므로 교착 상태 또는 확장성 버그를 찾아내는 데 매우 유용하게 사용할 수 있다.

위로

4. jmap(sun.tools.jmap)

해제되어야 하지만 해제되지 않고 있는 ArrayList(수천 개의 오브젝트가 있을 수 있음)와 같은 오브젝트 누수 문제가 발생할 수도 있다. 또는 가비지 콜렉션이 작동 중임에도 불구하고 확장 힙이 전혀 축소될 것처럼 보이지 않는 문제도 일반적으로 발생한다.

오브젝트 누수를 찾을 때는 지정된 시점의 힙에 대한 덤프를 작성한 다음 자세히 살펴보면 매우 유용한 정보를 얻을 수 있다.jmap은 힙의 스냅샷을 작성하여 이 기능의 첫 번째 역할을 수행한다. 다음으로 이후 섹션에서 설명하는 jhat 유틸리티를 사용하여 힙 데이터를 분석할 수 있다.

jmap은 이 기사에서 설명하는 다른 모든 유틸리티와 마찬가지로 매우 쉽게 사용할 수 있다. 스냅샷을 작성할 Java 프로세스의 VMID에 해당하는 jmap을 지정한 다음 생성된 결과 파일을 설명하는 일부 매개변수를 지정하면 된다. jmap에 전달할 매개변수는 덤프를 작성할 파일의 이름과 텍스트 파일 또는 2진 파일을 사용할지 여부로 구성되어 있다. 2진 파일은 가장 유용한 옵션이지만 인덱싱 도구와 함께 사용해야만 한다. 왜냐하면 몇 메가바이트에 달하는 내용이 16진수로 채워진 텍스트 파일을 수동으로 검사하는 것은 시간 낭비이기 때문이다.

Java 힙에 대한 일반적인 정보를 제공하기 위해 jmap은 -histo 옵션도 지원한다. -histo 옵션은 힙에서 현재 강력하게 참조된 오브젝트에 대한 텍스트 히스토그램을 생성하며, 이 히스토그램은 특정 유형에 사용되는 총 바이트 수에 따라 정렬된다. 특정 유형의 총 인스턴스 수도 제공하므로 일부 프리미티브 계산이 가능하고 인스턴스에 대한 상대적 비용도 추정할 수 있다.

아쉽게도 jmap에는 jstat와는 달리 period-and-max-count 옵션이 없지만 비교적 쉽게 쉘 스크립트나 다른 클래스의 루프에서jmap(또는 jmap.main())을 호출하여 스냅샷을 주기적으로 작성할 수 있다. (실제로 이 옵션은 OpenJDK 자체에 대한 소스 패치나 다른 유틸리티에 대한 확장으로 jmap에 추가할 수 있는 좋은 확장이다.)

위로

5. jhat(com.sun.tools.hat.Main)

힙을 2진 파일로 덤프한 후에는 jhat를 사용하여 2진 힙 덤프 파일을 분석할 수 있다. jhat는 브라우저에서 탐색할 수 있는 HTTP/HTML 서버를 작성하며, 이 서버는 지정된 시점의 힙에 대한 오브젝트별 보기를 제공한다. 힙을 오브젝트 참조별로 살펴볼 수도 있겠지만 이 보기는 전체 내용에 대한 자동화된 분석을 수행하는 데 많이 사용된다. 다행스럽게도 jhat는 OQL 구문을 지원하기 때문에 이러한 분석을 수행할 수 있다.

예를 들어, 다음과 같이 100개 이상의 문자가 포함된 모든 String에 대해 OQL 쿼리를 수행할 수 있다.

select s from java.lang.String s where s.count >= 100


결과가 오브젝트에 대한 링크로 표시되며, 그런 다음 이러한 링크는 해당 오브젝트의 전체 내용을 표시한다. 필드에서는 다른 오브젝트를 참조 해제할 수 있는 추가 링크로 참조한다. 또한 OQL 쿼리는 오브젝트 자체의 메소드를 호출하고 내장 쿼리 도구를 사용할 수 있다. 쿼리 도구인 referrers() 함수는 지정된 유형의 오브젝트를 참조하는 모든 참조자를 표시한다. 다음은 File 오브젝트에 대한 모든 참조자를 찾는 쿼리이다.

select referrers(f) from java.io.File f 


jhat 브라우저 환경의 "OQL Help" 페이지에서 OQL의 전체 구문과 그 기능에 대한 설명을 볼 수 있다. jhat와 OQL을 함께 사용하면 오작동이 발생한 힙을 구체적으로 정확하게 조사할 수 있다.

위로

결론

JDK의 프로파일링 확장은 Java 프로세스 내에서 진행 중인 작업을 자세히 파악할 필요가 있을 때 매우 유용하게 활용할 수 있다. 이 기사에서 소개한 모든 도구는 명령행에서 직접 사용할 수 있을 뿐만 아니라 JConsole이나 VisualVM과도 강력하게 결합된다. JConsole과 VisualVM은 Java 가상 머신에 대한 전반적인 보기를 제공하는 반면 jstat 및 jmap과 같이 특정 용도를 지닌 도구를 사용하면 세분화된 조사를 수행할 수 있다.

5가지 사항 시리즈의 다음 주제는 스크립팅이다.


참고자료

교육

  • 모르고 있던 5가지 사항(Ted Neward 저, developerWorks, 2010년): Java 플랫폼에 대해 모르는 것이 많았다는 것을 일깨워 주는 이 시리즈에서는 사소하게 여겼던 Java 기술을 유용한 프로그래밍 팁으로 바꿔준다. 

  • JDK Tools and Utilities: 이 기사에서 설명했던 실험 수준의 모니터링 및 문제점 해결 도구인 jpsjstatjstackjmap 및jhat에 대해 자세히 알아보자. 

  • "Top 10 Java Performance Troubleshooting Tools"(Sajan Kumar 저, Javalobby, 2008년 7월): JConsole이 이 목록의 첫 번째 항목이며 그 뒤로 독자들이 처음으로 보는 것일 수도 있는 수많은 도구가 있다. 

  • "Acquiring JVM Runtime Information"(Dustin Marx 저, Dustin's Software Development Cogitations and Speculations, 2009년 6월): jpsjinfo 및 JConsole과 같은 JDK의 내장 모니터링 및 관리 도구를 결합하는 여러 가지 방법에 대해 설명한다. 

  • "Java theory and practice: Urban performance legends"(Brian Goetz 저, developerWorks, 2003년 4월): Java 성능과 관련된 잘 알려져 있는 세 가지 "사실"에 대해 알아보자. 

  • "Java theory and practice: 메모리 누수와 약한 참조"(Brian Goetz 저, developerWorks, 2005년 11월): 의도하지 않은 오브젝트 유지의 일반적인 원인을 발견하고 해결하는 방법에 대해 알아보자. 

  • JVMTI(JVM Tool Interface): 프로파일링, 디버깅, 모니터링, 스레드 분석 및 커버리지 분석 도구를 지원하는 Java 플랫폼 기반의 프로그래밍 인터페이스에 대해 배워보자. JDI(Java Debug Interface)는 실행 중인 VM에 원격으로 액세스하는 데 필요한 디버거 정보를 제공한다. 

  • developerWorks Java 기술 영역: Java 프로그래밍과 관련된 모든 주제를 다루는 여러 편의 기사를 찾아보자. 

토론