자바성능튜닝 - JVM
HotSpot VM
- 자바 성능을 개선하기 위해 JIT(Just In Time) 컴파일러를 만들었습니다.
- JIT 컴파일러 : 프로그램의 성능에 영향을 주는 지점에 대해서 지속적으로 분석하여 분석된 지점은 부하를 최소하 하고 높은 성능을 내기 위한 최적화의 대상이 됩니다.
- HotSpot VM은 VM 런타임, JIT 컴파일러, 메모리 관리자로 구성되어져 있습니다.
- HotSpot VM 런타임에 GC방식과 JIT 컴파일러를 맞추어 사용할 수 있습니다.
- VM 런타임은 JIT 컴파일러용 API와 GC용 API를 제공하며 JVM을 시작하는 런처와 스레드 관리, JNI 등을 제공합니다.
JIT Optimizer
- Client 버전과 Server 버전으로 나뉘어져 있습니다.
- JIT는 각 메서드를 컴파일 할 만큼 시간이 충분하지 않습니다.
- 모든 코드는 초기에 인터프리터에 의해 시작되며 해당 코드가 많이 사용될 경우 컴파일 대상이 됩니다.
- HotSpot VM에서 이 작업은 각 메서드에 있는 카운터를 통해 통제 되며 두개의 카운터가 존재합니다.
* 수행 카운터 : 메서드를 시작할 때마다 증가
* 백에지 카운터 : 높은 바이트 코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때마다 증가
- 백에지 카운터는 메서드가 루프가 존재하는지를 확인할 때 사용되며 수행 카운터 보다 컴파일 우선순위가 높습니다.
- 이 값들이 한계치까지 도달하면 인터프리터가 컴파일을 요청합니다.
- 컴파일 요청이 되면 컴파일 대상 목록의 큐에 넣어지게 되고 스레드가 큐에서 꺼내 컴파일을 수행합니다.
- 인터프리터는 컴파일러 종료를 기다리지 않고 수행 카운터를 리셋 해 메서드 수행을 계속합니다.
*OSR(On Stack Replacement) : 인터프리터에서 수행한 코드 중 오랫동안 루프가 지속되는 경우에 사용되며 컴파일 되지 않고 코드가 수행되면 인터프리터에 머무르지 않고 컴파일 된 코드로 변경합니다. 루프가 끝나지 않고 지속적으로 수행되고 있을 경우에 큰 도움이 됩니다.
JRockit의 JIT 컴파일 및 최적화 절차
- JVM은 각 OS에서 작동할 수 있도록 자바 코드를 입력값으로 받아 각종 변환을 거쳐 해당 아키텍처에서 돌아가는 기계어 코드로 변환되어 수행되는 구조입니다.
1) JRockit runs JIT compliation
- 자바 애플리케이션을 실행하면 기본적으로 JIT 컴파일을 거친 후 실행이 됩니다. 이 단계를 거친 후 메서드가 수행되면, 그 다음부터는 컴파일 된 코드를 호출하기 때문에 처리 성능이 빨라집니다.
- 새로운 메서드가 수행되면 JRockit JVM이 느려집니다. JIT가 메서드를 수행하고 컴파일 하는 작업은 오버헤드 되지만 JIT가 없으면 JVM은 계속 느린 상태로 지속되고 처음만 느리고 지속적 수행에서는 더 빠른 처리가 가능합니다. 이러한 부분 때문에 모든 메서드를 처음에 호출하여 컴파일 하지 않습니다.
2) JRockit monitors threads
- JRockit에는 sampler thread가 존재하여 주기적으로 애플리케이션의 스레드를 점검합니다. 점검을 통해 어떤 메서드가 많이 사용되는지를 확인해 최적화 대상을 찾습니다.
3) JRockit JVM Runs Optimization
- 식별한 대상을 최적화 하며 백그라운드에서 진행되며 수행중인 애플리케이션에 영향을 주지 않습니다.
IBM JVM의 JIT 컴파일 및 최적화 절차
- 인라이닝 : 메서드가 단순할 때 적용되는 방식으로 호출된 메서드가 단수할 경우 그 내용이 호출한 메서드의 코드에 포함 시킵니다. 자주 호출되는 메서드의 성능이 향상됩니다.
- 지역 최적화 : 작은 단위의 코드를 분석하고 개선하는 작업을 수행합니다.
- 조건 구문 최적화 : 조건 구문을 최적화 하고 효율성을 위해 코드의 수행 경로를 변경합니다.
- 글로벌 최적화 : 메서드 전체를 최적화 하며 컴파일 시간이 많이 소요되지만 성능이 많이 개선됩니다.
- 네이티브 코드 최적화 : 아키텍처에 따라 최적화를 다르게 합니다.
JVM 시작 절차
1) java 명령어 줄에 있는 옵션 파싱
2) 자바 힙 크기 할당 및 JIT 컴파일러 타입 지정
3)CLASSPATH와 LD_LIBRARY_PATH 같은 환경변수 지정
4) Main클래스 확인
5) JNI_CreateJavaVM을 이용해 non-primordial 스레드에서 HotSpot VM 생성
6) HotSpot VM이 생성되고 초기화 되면 Main클래스가 로딩된 런처에서 main() 메서드 속성 정보를 읽음
7) CallStaticVoidMethod는 네이티브 인터페이스를 불러 HotSpot VM에 있는 main()메서드가 수행되며 이때 Main 클래스 뒤에 있는 값들이 전달
JVM 종료 절차
1) HotSpot VM이 작동중인 상황에서는 단 하나의 데몬이 아닌 스레드가 수행될 때까지 대기
2) Shutdown클래스의 shutdown()메서드가 수행되며 자바 레벨의 shutdown hook이 수행되고 finalization-on-exit이라는 값이 true일 경우에 자바 객체 finalizer를 수행
3) HotSpot VM레벨의 shutdwon hook을 수행함으로써 HotSpot VM의 종료를 준비
4) HotSpot의 exit()메서드를 호출하여 JNI처리 블록을 해제
5) HotSpot VM 스레드 종료
6) JNI, HotSpot VM, JVMTI barrier에 있는 추적기능 종료
7) 네이티브 스레드에서 수행하고 있는 스레드들을 위해서 HotSpot의 "vm exited" 값 설정
8) 현재 스레드 삭제
9) 입출력 스트림 삭제
10) JVM종료를 호출한 호출자로 복귀
클래스 로딩 절차
1) 주어진 클래스의 이름으로 클래스 패스에 있는 바이너리로 된 자바 클래스 찾음
2) 자바 클래스 정의
3) Class 클래스의 객체 생성
4) 링크 작업 수행 , 해당 단계에서 static 필드를 생성 및 초기화 하고 메서드 테이블 할당
5) 클래스의 초기화가 진행되며 클래스의 static 블록과 static 필드가 가장 먼저 초기화