Java JIT compiler
1. JIT 컴파일러란?
자바 파일을 컴파일하게 되면 ".class" 확장자를 가진 클래스 파일이 생성된다. 이 클래스 파일은 자바 바이트 코드(java byte code)로 이루어져 있다. 가상 기계(Virtual Machine)을 사용하는 언어에서의 가장 중요한 점으로는 컴파일된 바이트 코드를 어떻게 하면 더 효율적으로 빠르게 실행시킬 수 있을지에 대한 점이다.
자바 바이트 코드는 인터프리터 언어(interpreter language)이다. 한줄씩 읽고 해석하고, 그에 해당하는 기능을 실행시키는 인터프리터 언어이기에 비효율적으로 보일 수 있겠지만, 가상 머신에서 돌아가는 바이트 코드들은 매우 저수준의 인터프리터 언어이기에 자바스크립트 한줄에 비하면 자바 바이트 코드 한줄을 해석하고 실행하는데에는 큰 노력이 필요하지 않는다. 하지만, 아무리 그렇다 하더라도 기기에서 직접 돌아가는 기계어로 컴파일 되는 C/C++와 같은 언어들로 만든 실행 파일에 비하면 실행 속도가 느리다.
이러한 실행 속도의 문제를 해결하기 위해서 나온 것이 바로 JIT 컴파일러이다. JIT 컴파일러는 프로그램 실행 중에 (run time 중에) 가상 기계 상에서만 돌아가는 자바 바이트 코드를 해당 플랫폼에 맞는 기계어(native machine code)로 컴파일해 주는 특수한 컴파일러이다.
JIT 컴파일러가 바이트 코드를 기계어로 컴파일하는 시간만큼 프로그램의 실행 시간이 증가하게 된다. 하지만, 만약 매우 복잡하고 다양한 연산들을 하면서 자주 불려지는 메소드를 JIT 컴파일러로 컴파일 한다면, 실행 능력을 크게 향상시킬 수 있다.
또한, JIT 컴파일러는 바이트 코드를 컴파일하는 과정에서 몇 가지 간단한 최적화 과정을 거치게 된다. 그 최적화 과정들의 간단한 예시로는 스택 연산을 레지스터 연산으로 변환이나 레지스터 할당을 통한 메모리 접근 횟수 감소시키기 등이 있다.
2. JIT 컴파일러는 어떻게 실행되는가?
JIT 컴파일러는 JRE(Java Runtime Environment)의 일부로서, 자바 응용 프로그램의 성능을 실행 시간중에 향상시켜주는 도구이다.
알다시피, 자바 파일은 클래스로 이루어져 있으며, 플랫폼 독립적이다. 단, 컴파일된 클래스 파일들은 자바 가상 머신에 종속적이게 된다. 자바 응용 프로그램 실행 시에 자바 가상 기계는 해당 응용 프로그램의 실행에 필요한 클래스 파일들을 로딩한다. 프로그램을 실행하면서 추가로 더 필요한 클래스 파일등이 있을 경우에는 프로그램 실행 중에 해당 파일들을 읽어들인다. 이 과정에서 추가적으로 시간이 소모된다. 원래에도 명령을 하나씩 읽고 해석하면서 프로그램을 실행하기에, 추가 로딩이 필요한 경우에는 응용 프로그램의 속도가 그만큼 더 느려지게 된다. 이러한 느린 실행 속도를 향상시키기 위해서 존재하는 것이 바로 JIT 컴파일러이다. 실행에 비교적 많은 시간이 걸리는 바이트 코드를 프로그램 실행 중에 컴파일 함으로써 실행 속도를 향상시키는 것이다.
JIT 컴파일러의 사용 여부는 사용자가 직접 선택을 할 수가 있다. 다만, JIT 컴파일러가 언제나 응용 프로그램의 성능을 향상시켜주는 것은 아니기 때문에 작성한 코드를 잘 분석해보고 JIT 컴파일러의 필요 여부를 고려하고 사용하는 것이 좋다.
3. JIT 컴파일 방식의 이점
JIT 컴파일 방식과 사전 컴파일의 생성 코드의 품질을 비교하면 컴파일 시간에 대한 제약 때문에 JIT 방식이 불리하지만 유리한 점도 있다. 그것은 실행 환경에 맞추어 생성된 코드의 선택 및 최적화를 할 수 있다는 것이다.
인텔의 x86 CPU을 예로 들어 보면 IA-32 아키텍처의 범위 내에서 각 세대에 걸쳐 다양한 명령이 추가되었지만 응용 프로그램의 호환성을 유지하려면 실행 바이너리 안에서 80386과 호환되는 명령밖에 사용할 수 없다. 즉, MMX Pentium의 MMX 명령어를 포함한 코드는 80386 및 Pentium에서는 실행할 수 없다. 하지만 JIT 방식에서는 CPU가 MMX를 지원하고 있다면 MMX 명령어를 사용한 코드를 생성하고 그렇지 않으면 다소 비효율적인 Pentium 명령의 범위안에서 수행하도록 할 수 있다.
또한 실행 환경에서 캐시 및 메모리 크기, 속도특성 등도 실행해 보지 않으면 결국 알 수 없다. JIT 컴파일 방식은 실제로 처리중인 CPU, 메모리 정보를 알 수 있기 때문에 그에 따라 코드를 생성할 수 있어 사전 컴파일보다 뛰어난 코드를 생성할 가능성이 있다.
또한 객체 지향 언어의 실행하는 경우 가상 메소드 호출은 가상 함수 테이블을 통해 간접 호출되지만 동적 컴파일에서는 그 메소드를 오버라이드하는 서브 클래스가 존재하지 않는한 간접호출을 정적속박으로 호출하거나 또는 인라인으로 전개할 수 있다.
(그 메소드를 오버라이드하는 서브 클래스가 동적으로 로드될 가능성이 있지만 이 경우에는 컴파일 된 메소드는 최적화 복원(deoptimize) 될 필요가 있다)