kiwi

인터프리터 VS JIT 타이밍

by 키위먹고싶다

개요

처음 컴파일에 대한 개념을 알게 되었을 때, 자바는 인터프리터언어인가 컴파일언어인가를 살펴보던 중 자바는 인터프리터와 컴파일을 둘 다 사용하는 언어임을 알았었다.

바이트코드로 한번 컴파일된 클래스 파일을 클래스 로더를 통해 JVM에서 동적으로 로드시키고, JVM 내부에 있는 인터프리터와 JIT컴파일러로 최종 컴파일 되면서 기계어로 변환된다는 사실을 알고 있었다.

최근에 JVM에 동작방식에 대해 다시 공부하다가 인터프리터와 JIT컴파일러의 실행 타이밍에 대해 의문을 가지게 되었고, 이에 대한 내용을 정리하고자 한다.

 

컴파일러

자바는 javac컴파일러가 소스코드를 바이트코드로 이루어진 class파일로 변환시킨다. 따라서 JVM은 항상 바이트코드로 시작하며, 동적으로 기계에 의존적인 코드로 변환한다.

동적으로 기계에 의존?

JIT는 애플리케이션에서 각각의 메서드를 컴파일할 만큼의 시간적 여유가 없기 때문에 초기에는 인터프리터에 의해서 실행되고, 해당 코드가 충분히 많이 사용될 경우에 컴파일할 대상이 된다. HotSpotVM은 각 메서드에 있는 카운터를 통해서 통제되며, 메서드는 두 개의 카운터가 존재한다.

  • 수행 카운터 : 메서드를 시작할 때마다 증가
  • 백에지 카운터 : 높은 바이트 코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때마다 증가

백에지 카운터는 메서드가 루프를 포함하는지 확일할 때 사용되며, 수행 카운터 보다 컴파일 우선순위가 높다.

이 카운터들이 인터프리터에 의해서 증가될 때마다 그 값들이 한계치에 도달했는지를 확인하고, 도달했을 경우 인터프리터는 컴파일을 요청하게 된다. 수행 카운터의 한계치를 ComplileThreshold라고 한다.

 

백에지 카운터에서 사용하는 한계치 계산

💡 ComplileThreshold * onStackReplacePercentage / 100 만약 수행 카운터의 한계치가 35000이고 onStackReplacePercentage가 80이면 메서드가 35000번 호출되었을 때 JIT에서 컴파일을 하며, 백에지 카운터가 28000번이 되었을 때 컴파일한다.

 

보통의 인터프리터는 컴파일이 종료되기를 기다리지 않는 대신, 수행 카운터를 리셋하고 인터프리터에서 메서드 수행을 계속한다. 컴파일이 종료되면, 컴파일된 코드와 메서드가 연결되어 그 이후부터는 메서드가 호출되면 컴파일된 코드를 사용하게 된다.

 

정리하자면 바이트코드를 JIT에서 컴파일되기 전까지 인터프리터가 컴파일시키는 게 아니라 즉시 실행시켜 아웃풋으로 나타내고, 수행카운터와 백에지 카운터를 통해 메서드 호출이 임계치 값을 넘기면 JIT컴파일러가 최종적으로 컴파일시켜 그 이후부턴 메서드가 호출될 때마다 컴파일된 코드가 실행된다. 이전에 기억했던 JIT가 캐싱한다는 이 부분을 의미하는 것 같다.

'java' 카테고리의 다른 글

JDK vs JRE vs JVM  (0) 2023.06.24
[java] concurrent package 정리  (0) 2022.08.19
자바 동기화 클래스  (0) 2022.06.28
자바의 메모리 구조  (0) 2022.04.08
가비지컬렉터(Garbage Collector)  (0) 2022.02.05

블로그의 정보

kiwi

키위먹고싶다

활동하기