Warming up 필요한 이유
1. 클래스 로더 lazy-loading
클래스 로더가 JVM 으로 .class 파일을 로딩할 때 lazy-loading 을 한다.
프로세스가 동작중에 필요한 클래스가 있으면 그때 그때 로딩을 한다.
그러므로 최초 자바 프로세스가 떴을때 최초 호출되는 클래스는 로딩 시간이 필요하다.
2. JIT 컴파일
JVM 은 프로그램 목적 파일을 기계언어가 아닌 중간 언어인 바이트 코드로 컴파일한다.
이 바이트코드는 JVM 내의 인터프리터에 의해 그때 그때 기계어로 변환되고 실행된다.
(모든 코드는 초기에 인터프리터에 의해서 시작된다)
컴파일러 언어의 장점은 한 번에 기계어로 변환 하면서 코드 최적화를 수행할 수 있는데,
인터프리터 언어는 그게 불가능하다. 그래서 자바는 인터프리터 + 컴파일러 방식을 혼합해서 사용한다.
JIT 컴파일러는 동적으로 자주 실행되는 코드를 분석해 메서드 단위로 미리 컴파일하여 코드 캐시라는 곳에 캐싱해둔다.
그러므로 JIT 컴파일 시간이 추가로 소요되긴 한다.
HotSpotVM 주요 컴포넌트
- VM 런타임
- JIT 컴파일러
- 메모리 관리자
JIT 컴파일러 최적화 방법
- 각 메서드에 있는 카운터를 참조한다. 메서드에는 두 개의 카운터가 존재한다.
-
수행 카운터(Invocation counter) : 메서드를 시작할 때마다 증가
-
백에지 카운터(backedge counter) : 높은 바이트 코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때마다 증가
한계치공식:CompileThreshold∗OnStackReplacePercentage/100
- 이 카운터들이 인터프리터에 의해서 증가될 때마다, 그 값들이 한계치에 도달했는지 확인하고, 도달했을 경우 인터프리터는 컴파일을 요청한다.
JTI 컴파일러 동작 순서
- 인터프리터 컴파일 요청 → 큐 적재 → 컴파일러 스레드 모니터링 시작
- 요청이 완료되지 않으면 인터프리터는 수행 카운터를 리셋하고 인터프리터에서 메서드 수행을 계속한다.
- 컴파일이 종료되면, 컴파일된 코드와 메서드가 연결. 이후부터는 코드 캐시의 코드가 실행된다.
JVM 시작 절차 (Java.exe 프로세스)
- java 명령어
- heap 크기 할당, JIT 컴파일러 타입 지정
- 환경변수 지정
- 지정한 Main 클래스 확인. 없으면 Jar 파일의 manifest 파일에서 확인
- JNI_CreateJavaVM 으로 non-primordial 스레드에서 HotSpotVM 생성
- Main 클래스의 main 메서드 속성 정보 읽기
- JVM 의 CallStaticVoidMethod 의해 메인 메서드가 실행된다.
클래스 로딩 절차
loading → linking → initializing
중간 언어로 컴파일하는 이유
- 바이트 코드를 실행시에 기계어로 변환하게 함으로써 자바 플랫폼에 종속적이게 만들 수 있다.
- 이식성이 생김
GC
- full GC 를 수행하는 시점에는 해당 JVM 에서 처리되지 않기때문에 GC 가 자주 수행되면 응답 시간에 많은 영향을 끼친다.
- JVM 은 메모리를 GC 라는 알고리즘으로 관리한다. 더이상 참조되지 않는 고아객체를 메모리에서 제거해주는 작업이다.
JVM 의 runtime data area
- PC 레지스터
- JVM 스택
- heap ———→ GC 가 발생하는 영역
- 메서드 영역
- 런타임 상수 풀
- 네이티브 메서드 스택
GC
- 의존관계 최적화
- 메모리 파편화 방지
- GC Roots
- Stack 데이터
- 메서드 static 데이터
- JNI 로 만들어진 데이터
- GC 방식 5가지
- CG 알고리즘의 트레이트 오프는 주로 할당 및 수명과 연관되어있음
- JVM 은 에덴을 여러 버퍼로 나누어 각 스레드가 새 객체를 할당하는 구역으로 활용하도록 배포한다.