It is not inherently interpreted, but can just as well be implemented by compiling its instruction set to that of a silicon CPU. It may also be implemented in microcode or directly in silicon.
oracle official doc에서 해당 문구를 발견했다. java는 인터프리터, JIT 컴파일 방식을 주로 사용하며, AOT 컴파일 방식도 지원하는 것을 저번 Spring Boot 코드 분석을 하며 알게되었는데 실리콘 CPU의 명령어 집합 또는 마이크로 코드로 구현하거나 실리콘에 직접 내장할 수 있다는 내용이다.
그래서 호기심이 생겨 정리해보려 한다.
자바는 소스 코드가 실제 실행되기까지 두 번의 컴파일을 거치는 구조로, 이 과정은 자바의 플랫폼 독립성과 성능 최적화를 동시에 가능하게 한다. 두 번의 컴파일은 각각 정적 컴파일(AOT, Ahead-Of-Time)과 동적 컴파일(JIT, Just-In-Time)로 이루어지며, 아래와 같은 과정을 거친다.
두 번의 컴파일을 거치면서 자바 프로그램은 코드의 플랫폼 독립성을 유지하면서도 실행 시 성능을 최적화할 수 있다.
자바 소스 파일(.java)은 자바 컴파일러(javac)에 의해 플랫폼 독립적인 바이트코드로 변환되어 .class 파일이 된다.
이 바이트코드는 하드웨어에 직접 실행되는 것이 아닌, JVM이라는 가상 환경에서 실행될 수 있는 중간 코드이다.
바이트코드는 특정 CPU나 운영체제에 종속되지 않기 때문에, 이진 형태의 명령어(0과 1)로 이루어진 네이티브 코드와 달리 어떤 플랫폼에서도 동일하게 실행될 수 있는 특징을 가진다.
따라서 자바는 한 번 컴파일된 .class 파일을 여러 운영체제에서 실행할 수 있으며, 이를 통해 플랫폼 독립성을 달성한다.
JVM은 자바 바이트코드를 실제 실행할 수 있는 환경을 제공합니다. 여기서 중요한 것은 JVM 자체가 인터프리터와 JIT 컴파일러를 함께 사용하여 프로그램을 최적화한다는 점입니다. 인터프리터와 JIT 컴파일러는 각각의 역할을 담당하며, 특정 상황에 따라 유동적으로 작동합니다.
JVM이 프로그램을 처음 실행할 때는 주로 인터프리터 방식을 사용한다.
인터프리터 방식: 인터프리터는 .class 파일의 바이트코드를 한 줄씩 읽어 해석하고, 해석된 바이트코드를 곧바로 실행한다.
JVM은 프로그램 실행 중에 반복적으로 호출되는 코드 블록을 감지하면, JIT 컴파일러를 사용하여 네이티브 코드로 변환한다.
자주 사용되는 코드 최적화: 반복되는 루프나 자주 호출되는 메서드는 JIT 컴파일러에 의해 네이티브 코드로 변환되며, 이후에는 JVM이 해석 없이 곧바로 CPU에서 실행할 수 있다.
빠른 성능: JIT 컴파일은 JVM 메모리에 네이티브 코드를 저장해, 이후에는 이 코드가 직접 실행되므로 속도가 크게 향상된다.
예를 들어, 반복 루프나 이벤트 핸들러는 JIT 컴파일러가 이를 감지해 네이티브 코드로 변환하여 실행 속도를 최적화할 수 있다.
JVM은 프로그램의 실행 상황에 따라 최적화 전략을 실시간으로 조정한다. JVM이 프로그램을 실행하면서 메서드 호출이나 이벤트가 발생할 때마다 인터프리터와 JIT 컴파일러를 적절히 사용하여 프로그램을 최적화하는 것이다. 이를 통해 JVM은 유동적으로 성능을 최적화할 수 있다.
이러한 구조 덕분에 자바 애플리케이션은 초기 실행 시 빠른 속도로 시작하고, 실행 중에는 성능을 극대화하는 최적화 작업을 지속적으로 수행할 수 있다.
자료를 정리하다보니 내용이 너무 많아서 나중에 내용정리해서 올리겠습니다.