[Java] 자바는 컴파일 언어인가?

Sangho Han·2025년 2월 4일
2

☕️ Java

목록 보기
13/17
post-thumbnail

📝 정리

자바는 컴파일 언어가 아닌 하이브리드 언어이다

  • 자바는 WORA 원칙을 유지하기 위해, 원시 코드를 컴파일하여 .class파일로 변환한다.
  • 해당 바이트코드를 JVM이 한 줄씩, 즉 인터프리터 방식으로 해석하면서 실행하게 된다.
    • 하지만 인터프리터 방식으로만 실행하기에는 속도가 너무 느리다는 단점이 존재한다.
    • 때문에 JIT 컴파일러가 개입하게 된다.

JIT 컴파일러

  • JIT 컴파일러는 실행 중(Run TIme)에 일부 코드를 네이티브 코드로 변환하여 최적화시킨다.

    • 모든 코드에 대해 변환하는 것은 아니며, 반복적으로 실행되는 코드(핫스팟)에 대해서 변환 후 캐싱한다.
      • 컴파일 되어 캐싱한 네이티브 코드는, JVM의 메모리 내(JIT Code Cache 영역)에 저장된다.
      • 이는 프로그램이 실행되는 동안만 유지되며, 프로그램이 종료되면 사라진다.
        ⇒ 즉, 실행 속도는 거의 동일하다. 그러나 같은 코드더라도 JIT가 적용하는 최적화 방식과 타이밍이 달라질 수 있어 실행 속도가 완전히 같지 않을 수 있다.

    “변환한 네이티브 코드는 프로그램이 종료되면 사라진다. JIT는 네이티브 코드를 디스크에 저장하지 않고 메모리에만 저장하기 때문인데, 왜 그럴까?”

    • JIT는 실행 중에 성능을 분석하면서 최적화를 동적으로 조정할 수 있는데, 디스크에 네이티브 코드를 저장하게 되면 이러한 조정이 불가능해지기 때문이다.

    • 예를 들어, 인라이닝 기법을 사용하다가 실행 도중에 사용을 중지할 수 있다.

    • JIT는 반복적으로 호출되는 메서드에 대해서, 메서드 호출을 제거하고 메서드 내용을 직접 삽입하여 최적화 하는 인라이닝 기법을 사용한다.

      public static int calculate(int x) {
          return x * 2;
      }
      int result = calculate(i); // 매번 메서드 호출
      int result = i * 2;  // 메서드 호출이 사라지고 직접 연산
    • 컴파일 과정은 맞으나, C/C++과 같은 전통적인 방식과 다르게 전체를 한 번에 변환하는 것이 아니라 실행 중 일부만 변환하기 때문에 이를 동적 컴파일(Dynamic Compliation)이라고도 부른다.

      public class JITExample {
          public static void main(String[] args) {
              System.out.println("Hello, Java!");  // ✅ 실행 1번 (JIT 대상 X)
      
              for (int i = 0; i < 10000; i++) {   // 🔥 핫스팟 (JIT 변환 O)
                  calculate(i);
              }
      
              printMessage();  // ✅ 실행 1번 (JIT 대상 X)
          }
      
          public static void calculate(int x) {
              int result = x * 2;
          }
      
          public static void printMessage() {
              System.out.println("This is a message!");
          }
      }
      

    “왜 모든 코드를 JIT으로 컴파일하지 않을까?”

    1. 메모리 사용 문제

      • 모든 코드를 네이티브 코드로 변환하면 메모리 사용량이 증가한다.
      • 자바는 서버 환경에서도 사용되므로 메모리 관리가 중요하다.
    2. 컴파일 시간 오버헤드

      • JIT 컴파일에는 시간이 걸린다.
      • 사용되지 않는 코드를 굳이 네이티브 코드로 변환하면 불필요한 연산이 발생하기에, 실행 중 필요해지는 코드만 변환하는 게 효율적이다.
    3. 실행 방식의 유연성
      - JIT의 동적 컴파일과 인터프리터의 빠른 첫 실행의 이점을 동시에 취할 수 있다.

      ⇒ 즉, JIT과 인터프리터가 공존하는 이유는 성능 최적화메모리 절약을 동시에 하기 위해서이다.

결론적으로, Java는 바이트코드를 실행할 때 인터프리터 방식과 JIT 컴파일러가 공존하는 하이브리드 언어이다.

참고 블로그 1 - [Java] JIT 컴파일러란?


💡 느낀 점 및 배운 점

  1. 이전부터 Python은 인터프리터, Java는 컴파일 언어라는 생각을 가지고 그렇게 배웠었는데, 자세히 파고드니 그렇지 않다는 것을 알게 되어 유익했다.

  2. Java 파일이 컴파일 되고 해석되며 최종적으로 실행하는 일련의 과정을 알 수 있었다. 또한 JIT 컴파일러, 동적 컴파일에 대한 지식도 습득할 수 있었다.

  3. .class 파일을 한 번 까보면 재미있을 것 같다는 생각이 든다.

profile
안녕하세요. 비즈니스를 이해하는 백엔드 개발자, 한상호입니다.

0개의 댓글