HotSpot VM
- Sun에서 만든 성능 개선을 위한 JIT(Just In Time) 컴파일러 (HotSpot)
- 프로그램의 성능에 영향을 주는 지점에 대해 지속적으로 분석
- VM 런타임
- JIT 컴파일러
- 메모리 관리자
JIT Optimizer
- JIT 애플리케이션에서 각각의 메서드를 컴파일할 만큼 시간적 여유가 많지 않음
- 따라서 모든 코드는 초기에 인터프리터에 의해서 시작되고
- 해당 코드가 충분히 많이 사용될 경우에 컴파일할 대상이 된다
- HotSpot Vm에서는 메서드에 있는 두 개의 카운터에 의해 통제
- 수행 카운터
- 메서드를 시작할 때마다 증가
- CompileThreshold : 한계칙
- 벡에지 카운터
- 높은 바이트 코드 인덱스에서 낮은 인덱스 컨트롤 흐름이 변경될 때마다 증가
- 메서드가 루프가 존재하는지를 확인
- 수행 카운터
- 카운터들이 인터프리터에 의해 증가될 때마다 한계치에 도달했는지 확인후 컴파일 요청
- 컴파일이 요청되면 컴파일 대상 목록의 큐에 쌓이고, 하나 이상의 컴파일러 스레드가 이 큐를 모니터링
- 보통 인터프리터는 컴파일이 종료되기를 기다리지 않는 대신,
- 수행 카운터를 리셋하고 인터프리터에서 메서드 수행을 계속한다
- 컴파일이 종료되면, 컴파일된 코드와 메서드가 연결되어 그 이후부터는 메서드가 호출되면 컴파일된 코드를 사용
- OSR(On Stack Replacement)라는 특별한 컴파일도 수행
- 인터프리터에서 수행한 코드 중 오랫동안 루프가 지속되는 경우에 사용
- 만약 해당 코드의 컴파일이 완료된 상태에서 최적화되지 않은 코드가 수행되고 잇는 것을 발견한 경우
- 인터프리터에 계속 머무르지 않고 컴파일된 코드로 변경
JVM 시작 절차
- [1단계]
- java 명령어 줄에 있는 옵션 파싱
- 일부 명령은 자바 실행 프로그램에서 적절한 JIT 컴파일러를 선택하는 등의 작업을 하기 위해서 사용
- 다른 명령들은 HotSpot VM에 전달
- [2단계]
- 자바 힙 크기 할당 및 JIT 컴파일러 타입 지정
- 메모리 크기나 JIT 컴파일러 종류가 명시적으로 지정되지 않은 경우에 자바 실행 프로그램이 시스템의 상황에 맞게 선정
- [3단계]
- CLASSPATH와 LD_LIBRARY_PATH 같은 환경 변수를 지정
- [4단계]
- 자바의 Main 클래스가 지정되지 않았으면
- Jar 파일의 manifest 파일에서 Main 클래스를 확인한다
- [5단계]
- JNI의 표준 API인 JNI_CreateJavaVm를 사용하여 새로 생성한
- non-primordial이라는 스레드에서 HotSpot VM을 생성
- [6단계]
- HotSpot VM이 생성되고 초기화되면, Main
- 클래스가 로딩된 런처에서는 main() 메서드의 속성 정보를 읽는다.
- [7단계]
- CallStaticVoidMethod는 네이티브 인터페이스를 불러 HotSpot Vm에 있는 main()메서드가 수행된다.
- 이때 자바 실행 시 Main 클래스 뒤에 있는 값들이 전달된다.
5. 의 자바의 가상 머신(JVM)을 생성하는 JNI_CreateJavaVM 단계
- [1단계]
- JNI_CreateJavaVM는 동시에 두개의 스레드에서 호출할 수 없고,
- 오직 하나의 HotSpot VM 인스턴스가 프로세스 내에서 생성될 수 있도록 보장한다.
- HotSpot VM이 정적인 데이터 구조를 생성하기 때문에 다시 초기화는 불가능해서
- 오직 하나의 HotSpot VM이 프로세스에서 생성될 수 있다.
- [2단계]
- JNI 버전이 호환성 있는지 점검하고, GC 로깅을 위한 준비도 완료한다.
- [3단계]
- OS 모듈들이 초기화된다.
- 예를 들면 랜덤 번호 생성기, PID 할당 등이 여기에 속한다.
- [4단계]
- 커맨드 라인 변수와 속성들이 JNI_CreateJavaVM 변수에 전달되고,
- 나중에 사용하기 위해서 파싱한 후 보관한다.
- [5단계]
- 표준 자바 시스템 속성(properties)이 초기화된다.
- [6단계]
- 동기화, 메모리, safepoint 페이지와 같은 모듈들이 초기화된다.
- [7단계]
- libzip, libhpi, libjava, libthread와 같은 라이브러리들이 로드된다.
- [8단계]
- 시그널 처리기가 초기화 및 설정된다.
- [9단계]
- 스레드 라이브러리가 초기화된다.
- [10단계]
- 출력(output) 스트림 로거가 초기화된다.
- [11단계]
- JVM을 모니터링하기 위한 에이전트 라이브러리가 설정되어 있으면 초기화 및 시작된다.
- [12단계]
- 스레드 처리를 위해서 필요한 스레드 상태와 스레드 로컬 저장소가 초기화된다.
- [13단계]
- HotSpot VM의 '글로벌 데이터'들이 초기화된다. 글로벌 데이터에는 이벤트 로그(event log), OS 동기화, 성능 통계 메모리(perfMemory), 메모리 할당자(chunkPool)들이 있다.
- [14단계]
- HotSpot VM에서 스레드를 생성할 수 있는 상태가 된다. main 스레드가 생성되고, 현재 OS 스레드에 붙는다. 그러나 아직 스레드 목록에 추가되지는 않는다.
- [15단계]
- 자바 레벨의 동기화가 초기화 및 활성화된다.
- [16단계]
- 부트 클래스로더, 코드 캐시, 인터프리터, JIT 컴파일러, JNI, 시스템 dictionary, '글로벌 데이터' 구조의 집합인 universe 등이 초기화된다.
- [17단계]
- 스레드 목록에 자바 main 스레드가 추가되고 universe의 상태를 점검한다. HotSpot VM의 중요한 기능을 하는 HotSpot VM Thread가 생성된다. 이 시점에 HotSpot VM의 현재 상태를 JVMTI에 전달한다.
- [18단계]
- java.lang 패키지에 있는 String, System, Thread, ThreadGroup, Class 클래스와 java.lang의 하위 패키지에 있는 Method, Finalizer 클래스 등이 로딩되고 초기화된다.
- [19단계]
- HotSpot VM의 시그널 핸들러 스레드가 시작되고 JIT 컴파일러가 초기화되며 HotSpot의 컴파일 브로커 스레드가 시작된다. 그리고 HotSpot VM과 관련된 각종 스레드들이 시작한다. 이때부터 HotSpot VM의 전체 기능이 동작한다.
- [20단계]
- JNIEnv가 시작되며 HotSpot VM을 시작한 호출자에게 새로운 JNI 요청을 처리할 상황이 되었다고 전달해 준다.
JVM이 종료될 때의 절차
- HotSpot VM의 종료는 다음의 DestroyJavaVM 메서드의 종료 절차를 따른다.
- [1단계]
- HotSpot VM이 작동중인 상황에서는 단 하나의 nondaemon thread가 수행될 때까지 대기한다.
- [2단계]
- java.lang 패키지에 있는 Shutdown 클래스의 shutdown() 메서드가 수행된다.
- 이 메서드가 수행되면 자바 레벨의 shutdown hook이 수행되고,
- finalization-on-exit이라는 값이 true일 경우에 자바 객체 finalizer를 수행한다.
- [3단계]
- HotSpot VM 레벨의 shutdown hook을 수행함으로써 HotSpot VM의 종료를 준비한다.
- 이 작업은 JVM_OnExit() 메서드를 통해서 지정된다.
- 그리고 HotSpot VM의 profiler, stat sampler, watcher, garbage collector 스레드를 종료시킨다.
- 이 작업들이 종료되면 JVMTI를 비활성화하며 Signal 스레드를 종료시킨다.
- [4단계]
- HotSpot의 JavaThread::exit() 메서드를 호출하여 JNI 처리 블록을 해제한다.
- 그리고 guard pages 스레드 목록에 있는 스레드들을 삭제한다.
- 이 순간부터는 HotSpot VM에서 자바 코드를 실행하지 못한다.
- [5단계]
- HotSpot VM 스레드를 종료한다.
- 이 작업을 수행하면 HotSpot VM에 남아 있는 HotSpot VM 스레드들을 safepoint로 옮기고
- JIT 컴파일러 스레드들을 중지시킨다.
- [6단계]
- JNI, HotSpot VM, JVMTI barrier에 있는 추적(tracing) 기능을 종료시킨다.
- [7단계]
- 네이티브 스레드에서 수행하고 있는 스레드들을 위해서 HotSpot의 "vm exited" 값을 설정한다.
- [8단계]
- 현재 스레드를 삭제한다.
- [9단계]
- 입출력 스트림을 삭제하고 PrefMemory 리소스 연결을 해제한다.
- [10단계]
- JVM 종료를 호출한 호출자로 복귀한다.
클래스 로딩 절차
- [1단계]
- 주어진 클래스의 이름으로 class path에 있는 바이너리로 된 자바 클래스를 찾는다.
- [2단계]
- 자바 클래스를 정의한다.
- [3단계]
- 해당 클래스를 나타내는 java.lang 패키지의 Class 클래스의 객체를 생성한다.
- [4단계]
- 링크 작업이 수행된다. 이 단계에서 static 필드를 생성 및 초기화하고 메서드 테이블을 할당한다.
- [5단계]
- 클래스의 초기화가 진행되며 static 블록과 static 필드가 가장 먼저 초기화된다. 당연한 이야기지만 해당 클래스가 초기화 되기 전에 부모 클래스의 초기화가 먼저 이루어진다.
- loading -> linking -> initializing 로 기억하면 된다.
JVM에서의 예외처리
- JVM은 자바 언어의 제약을 어겼을 때 예외(exception)라는 시그널로 처리한다.
- HotSpot VM 인터프리터, JIT 컴파일러 및 다른 HotSpot VM 컴포넌트는 예외 처리와 모두 관련되어 있다.
- 예외를 발생한 메서드에서 잡을 경우
- 호출한 메서드에 의해서 잡힐 경우
- 후자의 경우, 보다 복잡하며 스택을 뒤져서 적당한 핸들러를 찾는 작업을 필요로 한다.
- 예외 경우
- 던져진 바이트 코드에 의해서 초기화될 수 있으며,
- VM 내부 호출의 결과로 넘어올 수도 있고,
- JNI 호출로부터 넘어올 수도 있고,
- 자바 호출로부터 넘어올 수도 있다.
- 가장 마지막 경우는 단순히 앞의 세가지 경우의 마지막 단계에 속함
- VM이 예외가 던져졌다는 것을 알아차렸을 때, 해당 예외를 처리하는 가장 가까운 핸들러를 찾기 위해서
- HotSpot VM 런타임 시스템이 수행된다.
- 이 때 핸들러를 찾기 위해서는 다음의 3개 정보가 사용된다.
- 현재 메서드
- 현재 바이트 코드
- 예외 객체
- 만약 현재 메서드에서 핸들러를 찾지 못했을 때는
- 현재 수행되는 스택 프레임을 통해서 이전 프레임을 찾는 작업을 수행한다.
- 적당한 핸들러를 찾으면, HotSpot VM 수행 상태가 변경되며,
- HotSpot VM은 핸들러로 이동하고 자바 코드 수행은 계속된다.
'개발서적 > 자바 성능 튜닝 이야기' 카테고리의 다른 글
[자성튜이] 반드시 튜닝해야 하는 대상 (0) | 2024.09.16 |
---|---|
[자성튜이] GC 발생 시점 (0) | 2024.09.16 |
[자성튜이] 서버를 어떻게 세팅해야 할까? (1) | 2024.09.16 |
[자성튜이] JSP와 서블릿, Spring (0) | 2024.09.16 |
[자성튜이] 로그 작성 (0) | 2024.09.16 |