자바는 OS에 독립적이다. 이게 무슨 뜻이냐면, 자바로 작성된 프로그램이 다양한 운영 체제에서 실행될 수 있기 때문이다.
자바는 바이트코드라는 중간 형태로 컴파일되며, OS에 맞는 JVM(Java Virtual Machine)이 이를 실행하기 때문에 여러 운영체제에서 동작할 수 있다.
즉, JVM은 OS마다 다르지만, 바이트코드는 모든 JVM에서 동일하게 실행될 수 있기 때문에 OS에 독립적이라고 할 수 있다.

개발자가 Java 소스 코드를 작성한다. 이때의 확장자는 .java이다.
Java Compiler가 자바 소스파일을 컴파일한다. 이때 .class로 변환되며, 이는 JVM에서만 읽을 수 있다.
.class 파일은 일반적인 CPU가 직접 실행할 수 없으며, JVM이 이를 해석(인터프리트)하거나 JIT 컴파일을 통해 변환해야 한다.컴파일된 바이트 코드(.class 파일)을 JVM의 클래스로더(Class Loader)에게 전달한다.
클래스로더의 역할
클래스로더는 컴파일된
.class파일을 로드하여 JVM 메모리에 적재하고 실행할 수 있도록 준비하는 역할을 한다.
클래스 로딩:
- 필요한
.class파일을 찾아 메모리에 로드한다.링크(Linking):
- 로드된 클래스는 세 가지 단계로 링크된다:
- 검증(Verification): 바이트코드가 자바 언어의 규칙에 맞는지 확인한다.
- 준비(Preparation): 클래스 변수를 위한 메모리를 할당하고 기본값으로 초기화한다.
- 해결(Resolution): 클래스 내부 및 외부 참조(라이브러리 포함)를 실제 메모리 주소로 변환한다.
초기화(Initialization):
- 클래스가 처음 사용될 때 정적 변수 및 초기화 블록을 실행한다.
JVM이 바이트코드를 실행 엔진을 통해 실행한다.
인터프리터 방식:
JIT(Just-In-Time) 컴파일러 방식:
- 프로그램 실행 초반: JVM은 실행 속도를 빠르게 하기 위해 처음에는 인터프리터 방식으로 실행한다.
- 실행 횟수가 적은 코드: 클래스 초기화 블록 등 몇 번 실행되지 않는 코드의 경우, JIT 컴파일을 하는 것보다 인터프리터로 실행하는 것이 효율적이다.
반복 실행되는 코드(HotSpot): JVM은 프로그램 실행 중 반복 호출되는 메서드를 감지하고, 해당 코드를 네이티브 코드(기계어)로 변환한다.
CPU가 직접 실행할 수 있도록 변환: 인터프리터 방식은 매번 바이트코드를 해석해야 하지만, JIT가 변환한 네이티브 코드는 한 번 변환 후 빠르게 실행할 수 있다.
- 초기 실행: JVM은 인터프리터를 사용하여 빠르게 프로그램을 시작한다.
- 핫스팟 감지: JVM이 반복 실행되는 코드를 감지하면, JIT 컴파일러가 이를 네이티브 코드로 변환한다.
- 성능 최적화: 이후 JVM은 변환된 네이티브 코드를 관리하며, CPU가 이를 직접 실행하도록 한다.
즉, Java에서는 인터프리터와 JIT 컴파일러가 협력하여 프로그램을 빠르게 실행하면서도 성능을 최적화한다.
이후, 위 그림에서 보듯이, JVM이 실행되는 환경인 런타임 시스템 → 운영체제(운영체제가 관리하는 자원 등 사용) → 하드웨어(최종적인 CPU가 네이티브 코드 실행)으로 자바 프로그램이 JVM에서 바이트코드를 해석하고 최적화한 후, 이를 운영체제와 하드웨어가 실행할 수 있도록 관리한다.