개요
흔히 개발자들이 작성하는 고수준 언어를 컴퓨터가 알아듣는 이진법의 저수준 기계어로 변환할 때 Compiler와 Interpreter 방식을 사용한다고 알고 있습니다. 그 중에서도, 자바의 JVM에서는 Interpreter 방식 뿐만 아니라 JIT 방식도 있다고 하는데, 정확한 차이를 잘 알지 못했습니다. 따라서 그 방식이 왜 나오게 돼었는지 이번 글에서 확인해보고자 합니다.
JIT Compiler
JIT은 Just In Time을 의미하는 용어로 JVM이 Byte-code를 해석할 때 사용하는 방식 중 하나로 Interpreter의 단점을 보완하기 위해 만들어진 Compiler입니다. 그렇다면 먼저 기존의 Interpreter 방식을 알아볼까요? Interpreter 방식은 Runtime 시, "명령어 단위"로 코드를 읽어서 해석한 후 실행하는 방식입니다. 아시다시피, 자바는 기계어코드를 직접 실행하지 않고 플랫폼에 독립적인 Byte-code를 JVM이 해석하는 방식을 택합니다. 따라서 이 코드에 대한 해석과 실행은 Runtime에 결정됩니다. 이 방식의 치명적인 단점은 중복되는 코드를 구분하지 못하고 해석, 실행과정을 똑같이 진행하기 때문에 작업 시간이 많이 들고 성능이 지연됩니다.
그렇다면 JIT은 어떤 방식으로 일을 할까요? 눈치를 채신 분들도 있겠지만 Interpreter는 중복된 코드들을 마치 처음 본 것 처럼 계산을 하기 때문에 JIT은 중복을 효율적으로 처리하는 컨셉을 가지고 있습니다. JIT을 설명하기 전에 결론부터 말하자면 JVM에서는 ByteCode를 해석할 때 JIT만 사용하는 것은 아니고 "Interpreter방식"과 "JIT 방식"을 혼용해서 사용합니다.
아래의 사진으로 JIT 동작 방식을 살펴보겠습니다.
JVM는 중복 코드를 효과적으로 처리하기 위해 Byte-code를 해석할 때 처음 본 코드(First use), 이전에 본 코드(Succesive use) 2가지로 구분을 합니다.
First use는 처음 본 코드에 대한 과정이며, JIT Compiler가 Byte-code를 컴파일하여 Native Code(사실상 Machine Code)로 바꾸고 Cache에 저장합니다. Cache의 위치는 JVM의 Method Area에 코드 캐시입니다.
Successive use에서는 해석해야 할 명령어가 이미 Cache에 저장되어 있는 경우에 해당하며, 별도의 컴파일 과정을 거치지 않고 이미 compile 된 Native Code를 바로 얻을 수 있습니다.
즉, 평소에는 Interpreter처럼 명령어 단위로 해석을 하다가 중복된 부분이 있으면 JIT Compiler를 통해서 미리 저장된 코드를 이용하여 성능을 향상합니다.
그렇다고 항상 Interpreter방식 보다 JIT Compiler방식이 성능이 뛰어나다고 확신할 수 없습니다. 왜냐하면 일반적으로 반복되지 않는 코드들에 대해 JIT Compiler를 통해 compile되고 실행되는 시간은 Interpreter를 통해 compile되고 실행되는 과정보다 빠르다고 보장할 수 없습니다. 반복되는 코드가 많아질수록 JIT Compiler 덕분에 성능이 좋아집니다. 따라서 JVM 내부적으로 어떠한 코드들이 있는지 미리 확인하고 어떤 방식을 사용할지 결정해야 합니다. 이는 JVM의 Profiler에서 확인합니다. 실행 시간을 결정짓는 중요 명령어와 반복 명령어에 대해 미리 정적(static) 컴파일을 진행합니다.
참고
'학습' 카테고리의 다른 글
TreeMap vs HashMap vs LinkedHashMap (0) | 2020.08.25 |
---|---|
Interface vs Abstract Class (0) | 2020.08.23 |
Hash 충돌 회피 알고리즘 (0) | 2020.07.24 |
Iterator & Enumeration & ListIterator (0) | 2020.07.23 |
Fail-Fast vs Fail-Safe (0) | 2020.07.23 |