Java

[ Java ] 자바 컴파일에 대한 이해

구티맨 2020. 11. 6. 10:57

컴파일러 란?

 

흔히 컴파일러란, 고차원의 언어를 저차원의 즉 기계어로 옮기는 과정으로 이해를 하고 있습니다.

 

여기서 기계어란 H/W 가 이해할 수 있는 어셈블리어를 떠오르게 됩니다.

 

이게 전혀 틀린 말은 아니지만, JAVA 의 컴파일을 보다보면 조금 의아한 의문점이 생깁니다.

 

컴파일러에 대해 다시 한번 보고, C 언어의 컴파일과 JAVA 의 컴파일에 대해서 한번 알아 보겠습니다.


위키에 컴파일러를 찾아보면, 특정 프로그래밍 언어로 쓰고 있는 문서를 다른 프로그래밍 언어로 옮기는 프로그램을 뜻하며,

 

소스코드( 원시 코드 )를 목적코드로 출력한다고 합니다.

 

C 계열의 언어를 예로 들면 C언어를 컴파일 하면

 

기계어인 어셈블리어로 변환이 되며 이는 H/W에서 이해할 수 있는 언어입니다.

 

자바에서 컴파일을 하면 바이트코드가 생성되며, 이는 H/W가 이해할 수 있는 없는 언어 입니다.

 

좁은 의미에서 컴파일러가 고차원의 언어( C, JAVA 등 )를 기계어( 어셈블리어 ) 를 만든다는 뜻은 맞으나

 

자바를 봤을 때는 물음표가 떠오르게 되죠.

 

하지만 위키에서의 정의를 알고 있다면, JAVA 라는 언어를 쓴 문서를

 

다른 프로그래밍 언어인 바이트코드로 옮겨졌으니 맞는 의미입니다.

 

자 ,그럼 JAVA의 컴파일한 기계어(바이트코드)는 누가 이해할 수 있는 언어일까요?

 

바로 JVM 입니다. Java Virtual Machine.

 

여기까지 읽고 다시한번 컴파일러의 좁은 의미인 "기계어로 만든다"의

 

기계가 반듯이 H/W 를 뜻하지 않는다는 것을 알 수 있습니다.

 

JAVA 에서 컴파일된 기계어의 기계는 Java Virutal Machine 을 뜻하는 것이죠.

 

그럼 바이트 코드는 어떻게 H/W에서 수행이 되는걸까요?

JVM Architecture

출처 : https://blogitwithsatyam.com/2018/06/19/jvm-architecture-in-depth/

위의 그림은 JVM 아키텍처 입니다.

 

바이트코드가 어떻게 기계어로 변환이 되는지 그림과 함께 설명을 해보겠습니다.

 

앞서 설명한 대로 JAVA 코드가 컴파일러(javac)에 의해 컴파일을 하여 .class( 바이트코드 ) 파일을 만들고,

 

이 파일이 JVM의 Class Loader에 로딩이 되고,

 

Execution Enginge에서 동적으로 인터프리터나 JIT 컴파일러에 의해 기계어로 변환을 하게 됩니다.

 

인터프리터와 JIT 컴파일러

 

인터프리터나 JIT 컴파일러에서 기계어로 변환을 한다고 했는데 왜 2가지가 존재할까요?

 

바로 성능 때문입니다.

 

인터프리터는 한줄씩 읽어 변환을 하고, JIT 컴파일러는 바이트코드 전체를 읽어 모두 변환을 합니다.

 

매번 코드를 한줄씩 읽어 변환을 하게 되면, 아무래도 미리 컴파일된 코드를 수행하는 것 보다 느릴 수 밖에 없습니다.

 

그렇다고, 바이트 코드 전체를 프로그램 수행 초기에 모두 컴파일을 하게 되면 초기 속도가 너무 느리게 됩니다.

 

그렇기 때문에 모든 코드는 초기에 인터프리터에 의해 시작되고,

 

해당 코드가 충분히 많이 사용될 경우에 JIT 컴파일러에서 컴파일을 수행하게 됩니다.

 

JIT 컴파일러는 Sun에서는 성능 개선을 위해 만들었고 이름을 HotSpot으로 지었습니다.

 

HotSpot은 JAVA 1.3 버전부터 기본 VM으로 사용되어 왔고 이를 HotSpot(기반의) VM 이라고 부릅니다.

 

HotSpot VM에서는 각 메서드에 있는 카운터를 통해서 통제가 되며, 메서드에는 invocation counter, backedge counter 가 존재합니다.

 

invocation counter : 메소드를 시작할 때마다 증가

backedge counter : 높은 바이트 코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때 마다 증가

 

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

 

JIT 컴파일러에서 컴파일이 완료되면, 메서드가 호출 되때 컴파일된 코드를 사용하게 됩니다.

 

이 카운터의 한계치 값은 JVM 옵션에서 지정할 수 있으면

invocation counter는 XX:CompileThreshold, backedge counter는 XX:OnStackReplacePercentage 옵션으로 지정 가능합니다.

( backedge counter = CompileThresHold * OnStackReplacePercentage / 100 )