kok202
JIT 컴파일러

2020. 4. 22. 04:24[정리] 기능별 개념 정리/JVM

HotSpot VM

자바를 만든 Sun 에서는 자바의 성능 개선을 하던 도중 JIT 컴파일러를 개발한다. 그리고 이 이름을 HotSpot 이라고 짓는다.

JIT 컴파일러는 아래에서 설명하겠지만, JVM 환경에서는 javac 가 컴파일한 바이트 코드를 읽고 실행하는 인터프리터가 존재한다.

이 때 인터프리터가 자주 수행되는 영역이 있다 하면 이를 hot code 영역이라고 부른다.

JIT 컴파일러는 hot code 영역을 효율적으로 다루기 위해 만들어진 기능이다.

이렇게 HotSpot(JIT 컴파일러)이 탑재된 VM 은 자바 3 부터 기본 VM 으로 사용되어 왔다.

HotSpot VM 에는 주요 3가지 컴포넌트가 존재한다.

  • JIT 컴파일러
  • VM 런타임
  • 메모리 관리자

번외) HotSpot VM 에는 OSR 컴파일 기능도 존재한다. 이는 인터프리터가 오랫동안 루프를 돌고 있을 때 사용할 수 있다. 해당 코드가 컴파일이 완료된 상태에서 컴파일 되지 않은 코드가 수행되고 있음을 감지하면 이를 컴파일된 코드로 변경시킨다. 이 기능은 무한 루프를 사용하는 경우 큰 도움이 된다. 

 

 

 

JIT 컴파일러

출처 : https://aboullaite.me/understanding-jit-compiler-just-in-time-compiler/

 

자바는 javac 라는 컴파일러를 사용한다, javac 는 소스 코드(.java)를 바이트 코드(.class)로 변환한다. 이렇게 만들어진 바이트 코드는 당연하게도 아무 머신에서나 실행 할 수 있는 코드가 아니다. 이 바이트 코드는 JVM 에 있는 인터프리터를 통해서 읽혀지면서 JVM 이 동작하고 있는 머신에서 실행 가능한 native 코드로 변환되어 수행된다. 이 때 특정 영역의 코드가 자주 읽히게 되면 그 영역을 hot code 영역이라고 부른다. JIT 컴파일러는 이 hot code 영역을 좀 더 효율적으로 다루기 위해 등장한 컴파일러다. 인터프리터가 바이트 코드를 읽던 도중 어떤 영역을 hot code 영역이라고 판단하면 JIT 컴파일러에게 컴파일 요청을한다. JIT 컴파일러는 이 요청을 받고 바이트 코드를 최적화해서 native 코드로 컴파일 한다. 이렇게 만들어진 native 코드는 이후 같은 hot code 영역을 읽으려 할 때 인터프리터를 사용하지 않고 최적화된 native 코드를 수행할 수 있도록 한다.

 

JIT 컴파일러는 바이트 코드를 읽으면서 어떤 메소드가 많이 사용되는지 확인하고 많이 사용되는 메소드가 있다면 이를 컴파일 대상으로 삼는다. 여기서 말하는 컴파일이란 단순히 인터프리터가 native 코드로 변환했던 것을 의미하는 게 아니라 최적화된 native 코드를 얻어내는 것을 의미한다. 이렇게 컴파일된 native 코드는 캐싱되어 이후에 번역을 할 필요가 없어진다.

 

JIT 컴파일러는 메소드가 얼마나 자주 호출되는지 판단하기 위해서 메소드 별로 각자 두 개의 카운터를 가지고 있는다.

  1. 수행 카운터 : 메소드 실행시 증가
  2. 백에지 카운터 : 높은 바이트 코드 인덱스에서 낮은 바이트 코드 인덱스로 컨트롤 흐름이 변경될 때 증가

이 카운터들이 한계치에 도달하면 인터프린터는 컴파일을 요청한다. 컴파일 요청은 큐에 쌓인다. 컴파일러 스레드는 이 큐를 모니터링하고 있다가 바쁘지 않을 때 큐에서 pop 하여 컴파일을 한다. 런타임 시에는 컴파일러 스레드의 컴파일 결과를 무작정 기다리지 않는다. 그저 이전과 같이 묵묵히 인터프리터를 수행시키다 컴파일이 완료되면 컴파일된 코드를 사용하게된다. (-Xbatch 나 -XX:-BackgroundCompilation 옵션으로 컴파일을 기다리게 변경도 가능하긴하다.)

 

한계치를 유도하는 공식은 아래와같다.

CompileThreshold * OnStackReplacePercentage / 100

각각의 값은 JVM 시작시 옵션으로 지정가능하다.

XX:CompileThreshold=35000

XX:OnStackReplacePercentage=80

이는 곧 메소드가 35000번 수행되거나 백에지 카운터가 28000(35000 * 0.8)이 되었을 때 컴파일을 하라는 의미다.

 

JIT 컴파일러는 프로그램의 성능에 영향을 주는 지점을 지속적으로 분석한다.

분석된 지점의 부하를 최소화하고 높은 성능을 내기 위해 최적화대상으로 삼는다.

JIT 컴파일러는 클라이언트 버전과 서버 버전으로 나뉜다.

 

 

 

JIT 컴파일 최적화 절차

오라클 JVM  은 JIT 컴파일러 최적화 관련 문서가 존재하지 않아서 JRockit JVM과 IBM JVM 을 예시로 든다.

컴파일 된 코드는 코드 캐시라고 불리는 JVM 프로세스 영역에 저장된다.

즉 JVM 프로세스는 JVM 수행 파일과 컴파일된 JIT 코드(native code) 집합으로 분류된다.

 

JRockit JVM 최적화 절차

  1. JRockit JVM 은 자바 어플리케이션을 실행하면 기본적으로 JIT 컴파일을 거쳐서 native 코드가 된 상태로 실행된다. 단 이 때 컴파일 된 코드는 최적화가 된 것은 아니다. 덕분에 run time 속도는 올라가지만 시작 속도가 느리다. 이 시점에 최적화도 같이하면 되는 것 아닌가 하고 생각할 수 있겠지만 꼭 그렇지많은 않다. 모든 메소드를 최적화 하는 것은 JVM 의 실행 시간을 느리게 할 수 있다.
  2. JRockit JVM 은 JVM 이 실행하고 있는 스레드를 모니터링한다. 이 스레드를 샘플러 스레드라고 한다. 샘플러 스레드는 주기적으로 어플리케이션의 스레드를 감시한다. 그리고 어떤 스레드가 동작 중인지와 수행 내역을 관리한다. 이 정보를 바탕으로 많이 사용되는 메소드를 선정하여 최적화 대상으로 삼는다.
  3. 최적화 대상이 된 메소드들을 최적화한다. 이 작업은 백그라운드로 실행되어 어플리케이션을 방해하지 않는다. 최적화 절차는 아래와 같다.
    1. final 로 선언된 메소드는 인라인 처리한다.
    2. 불필요한 부하, 메모리 생성을 제거한다.
    3. 불필요하게 생성된 변수를 대체한다.
    4. 죽은 코드를 삭제한다.

IBM JVM 최적화 절차

IBM JVM 의 JIT 컴파일 방식은 아래 5가지이다.

  • 인라이닝 : 메소드가 단순할 때 적용된다. 호출된 메소드를 호출한 메소드에 포함 시켜버려서 메소드 호출을 없애 성능을 높인다.
  • 지역 최적화 : 작은 단위의 코드를 분석, 개선한다.
  • 조건문 최적화 : 메소드 내의 조건문을 최적화하고 효율성을 위해 코드의 수행 경로를 변경한다.
  • 글로벌 최적화 : 메소드 전체를 최적화한다. 컴파일 시간이 많이 소모되지만 성능 개선을 많이 할 수 있다.
  • 네이티브 코드 최적화 : 이 경우 플랫폼별로 아키텍쳐에 따라서 최적화 방식이 다르다.

 

 

 

'[정리] 기능별 개념 정리 > JVM' 카테고리의 다른 글

GC  (0) 2020.04.25
JVM 의 실행, 종료, 클래스 로더, 아키텍쳐  (0) 2020.04.23
Garbage collection 요약  (0) 2020.03.17