JIT 컴파일 또는 동적 번역은 프로그램을 실제 실행하는 시점에 기계어로 번력하는 컴파일 기법이다.
컴파일러 와 인터프리터
컴파일과 인터프리터 모두 high-level language를 machine language로 번역한다
컴파일러는 소스 코드 전체(우리가 사용하는 코드)를 한번에 번역하여 목적 파일(기계어)로 만들어 메모리에 적재한다.
인터프리터는 소스 코드를 한 줄 한 줄씩 중간 코드로 번역을 한 후에 실행한다.
- 컴파일러는 소스 코드 전체를 컴퓨터 프로세서가 실행할 수 있도록 바로 기계어로 변환하게 된다.
인터프리터는 high-level 언어를 중간 코드로 변환하고 이를 각 행마다 실행한다 - 일반적으로 컴파일러가 각 행마다 실행하는 특성을 가진 인터프리터보다 실행시간이 빠르다.
- 컴파일러는 전체 소스코드를 변환 한 뒤 에러를 보고 하지만 인터프리터는 각 행마다 실행하는 도중 에러가 보고되면 이후 작성된 코드를 살펴보지 않는다. 이는 보안적인 관점에 도움이 될 수 있다.
- 위에 그림에도 예가 적혀있지만 Java는 특이하게 인터프리터와 컴파일러 두개를 모두 사용한다.
각각의 장단점
컴파일러
- 장점
- 컴파일이 완료된 실행 파일은 컴퓨터에서 빠르게 실행할 수 있기 때문에 효율적이다.
- 0과 1로 된 기계어로 번역되기 때문에 프로그램의 코드가 유출되지 않는다.
- 컴파일 에러와 관련된 에러를 초기에 발견할 수 있다.
- 단점
- 코드를 수정하면 컴파일을 다시 해야한다
- 소스 파일 전체를 컴파일해야 하므로 용향이 크다 때문에 수정 사항이 빈번할 경우 문제가 발생 할 수 있다.
- 모든 소스 파일을 한꺼번에 번역하기 때문에 컴파일 시간이 비교적 느리다.
- 목적 파일 생성을 위해 메모리를 사용한다
- 특정 시스템에서 만들어진 실행 파일이 다른 시스템에서는 실행되지 않는 경우가 많다.
인터프리터
- 장점
- 메모리를 사용하지 않는다.
- 시스템 간의 이식성이 뛰어나다.
- 전체 코드를 다시 컴파일할 필요가 없기 떄문에 코드 수정이 용이하다
- 단점
- 매번 번역 과정을 거쳐야 하기 때문에 실행 속도가 컴파일보다 느리다
- 중간 코드로 해석되기 때문에 프로그램의 코드가 유출될 수 있다.
Java의 컴파일러와 인터프리터
자바의 코드가 실행하는 과정을 짧게 보자면
- 자바의 파일(.java)을 자바 컴파일러를 통해 바이트코드 파일로 컴파일 한다.
- JVM의 실행 엔진 내부에 있는 자바 인터프리터를 통해 바이트 코드를 특정 환경의 기계어로 번역하고 실행한다
자바 컴파일러의 사용 이유
자바는 WORA(Write Once Run Anywhere)을 구현하기 위해 물리적인 머신과 별개의 가상 머신인 JVM을 기반으로 동작하도록 설계하였다. 그래서 자바 바이트 코드를 실행하고자 하는 모든 하드웨어에 JVM을 동작시킴으로써 자바 실행 코드를 변경하지 않고도 모든 종류의 하드웨어에서 동작되게 한 것이다. java의 소스코드 즉, .java파일은 CPU가 인식을 하지 못하므로 기계어로 컴파일을 해주어야 한다. 하지만 Java는 JVM이라는 가상 머신을 거쳐서 OS에 도달하기 때문에 OS가 인식할 수 있는 기계어로 바로 컴파일 되는 것이 아닌 JVM이 인식할 수 있는 자바 바이트코드로 변경되는 것이다.
자바 컴파일러는 .java 파일을 .class 파일인 "자바" 바이트 코드로 변환시켜준다
JVM의 실행 엔진은 왜 인터프리터인가
- 인터프리터는 플랫폼에 종속되어있지않다
- 컴파일러의 경우는 프로그램이 작성된 기계상에서 매우 효율적으로 실행된다. 이는 대부분의 하드웨어 제어 시스템의 프로그래민 언어가 C인 이유가 된다. 하지만 이와 동시에 기계에 종속된다는 말도 된다는 것이다.
자바 인터프리터는 자바 컴파일러를 통해 생성된 바이트 코드를 한 줄 씩 읽어 기계어로 번역하고 실행하게 되는데 이는 WORA의 철학과 비슷하다. 만약 JVM 내에서 컴파일러를 사용해서 바이트 코드의 목적 파일을 생성한다면 이는 기계에 종속되는 파일이기 떄문이다.
하지만 이와 모순되게 인터프리터는 컴파일러보다 실행 속도가 느리다. 때문에 이를 위한 해결책으로 JVM은 부분적으로 JIT 컴파일러를 사용해서 바이트코드를 컴파일해서 사용한다.
- 컴파일러의 경우는 프로그램이 작성된 기계상에서 매우 효율적으로 실행된다. 이는 대부분의 하드웨어 제어 시스템의 프로그래민 언어가 C인 이유가 된다. 하지만 이와 동시에 기계에 종속된다는 말도 된다는 것이다.
- 초기 실행 속도를 빠르게 진행 할 수있다
- 자바 바이트코드 전체를 프로그램 수행 초기에 모두 읽어 컴파일하게 되면 초기 실행 속도가 느리다. 인터프터를 사용하면 초기 실행 속도를 높일 수 있다.
- 보안적으로 장점이 존재한다
- 자바 바이트 코드는 컴퓨터와 프로그램 사이에 별도의 버퍼 역할을 한다. 인터넷이나 기타 매체를 통해 신뢰할 수 없는 프로그램을 다운로드하여 실행하는 경우 어느 정도 안전이 보장될 수 있다. 자바 인터프리터를 사용함으로써 바이러스나 기타 악성 프로그램에 대응하는 가드 같은 보안 계층에 의해 보호 될 수 있는 것이다
JIT 컴파일러
위에서 말한 것처럼 자바는 코드를 실행하기 위해 바이트 코드로 컴파일하는 과정과 바이트 코드를 인터프리트하는 과정을 거쳐야하기 때문에 컴파일 과정만 필요한 다른 프로그래밍 언어보다 느리다. 거기에 더해서 인터프리터는 컴파일러보다 느리기 떄문에 성능상의 문제가 발생한다.
이를 해결하기 위해 JIT 컴파일러가 나왔다. 원래 JVM에서는 인터프리터 방식만을 사용했지만 JIT 컴파일러 덕분에 성능이 좋아졌다고 할 수 있다.
JIT 컴파일러의 동작 방식
JIT 컴파일러는 실행 시점에서는 인터프리터와 같이 기계어 코드를 생성하면서 해당 코드가 컴파일 대상이 되면 컴파일하고 그 코드를 캐싱한다. JIT 컴파일은 코드가 실행되는 과정에 실시간으로 일어나고(그래서 Just-In-Time) 거기서 전체 코드의 필요한 부분만 변환하게 된다. 기계어로 변환된 코드는 캐시에 저장되기 떄문에 재사용시 컴파일을 다시 할 필요가 없다
- JIT 컴파일러가 컴파일하는 조건은 얼마나 자주 코드가 실행됬는가이다.
일정한 횟수 만큼 실행되고 나면 컴파일 임계치에 도달하고 컴파일러는 컴파일하기에 충분한 정보가 쌓였다 판단한다. - 임계치는 메소드가 호출된 횟수, 메소드의 루프를 빠져나오기까지 돈 횟수 두개를 기반으로 한다.
이 두 수의 합계를 확인하고 메소드가 컴파일 될 자격이 있는지 여부를 결정한다. 자격이 있다면 메소드는 컴파일 되기 위해 큐에서 대기하게 된다. 이후 메소드들은 컴파일 스레드에 의해 컴파일 된다. - 아주 오랫동안 돌아가는 루프 문의 카운터가 임계치를 넘어가면 해당 루프는 컴파일의 대상이 된다
JVM은 루프를 위한 코드의 컴파일이 끝나면 루프가 다시 반복될떄는 코드를 컴파일된 코드로 교체하고 더 빠르게 실행된다. 이 교체 과정을 "스택 상의 교체(ORS)" 라고 부른다
그림으로 보면 좀 더 이해하기 좋을 수도 있다. 반복되는 코드들을 컴파일을 해서 캐싱을 하고 인터프리터는 반복되는 코드를 일일히 읽는 것이 아닌 컴파일된 코드를 바로 읽어올수 있는 것이다.
JIT 컴파일러의 장점
일반적인 인터프리터 언어는 바이트 코드나 소스코드를 최적화 과정이 없이 번역하기 떄문에 성능이 낮다. 반면 정적으로 컴파일하는 언어는 실행 전에 무조건 컴파일을 해야하기 때문에 다양한 플랫폼에 맞게 컴파일을 하려면 시간이 오래걸린다.
JIT 컴파일러는 실행 과정에서 컴파일을 할 수 있기 위해 만들어졌다. JIT 컴파일러는 정적 컴파일러만크 ㅁ빠르면서 인터프리터 언어의 빠른 응답속도를 추구하기 위해 사용한다. 또한 바이트 코드 컴파일러가 시간이 많이 소요되는 최적화를 미리 해주기 때문에 바이트 코드에서 기계어 번역은 훨씬 빠르게 진행될 수 있어 성능상의 이점이 있다.
'CS > 📝 언어' 카테고리의 다른 글
🖨 new String() 과 ""의 차이 (0) | 2024.06.04 |
---|---|
🖨 절차 지향과 객체 지향 프로그래밍의 차이 (0) | 2024.05.22 |
🖨 OOP란?? (0) | 2024.05.22 |
추상 클래스와 인터페이스의 차이 (0) | 2023.10.26 |
Java의 컴파일 과정 & JVM의 구조 (0) | 2023.10.18 |