[JVM 완전정복 #1] JVM 구성요소 총정리: 가상 머신이 자바 코드를 실행하는 비밀

2025. 11. 11. 17:32·자바

0. 들어가며

자바는 “한 번만 컴파일하면 어디서나 실행된다(Write Once, Run Anywhere)”는 말로 유명합니다.
그런데 이 말을 가능하게 만드는 주인공이 바로 JVM(Java Virtual Machine) 입니다.

평소엔 그냥 지나쳤던 JVM이 어떻게 동작하는지 그리고 내부에서 어떤 구조로 자바 코드를 실행하는지 궁금해서 이번에 정리해봤습니다.

 

JVM을 공부하다 보니 한 번에 다 다루기엔 내용이 너무 방대하더라고요.그래서 이번 글에서는 먼저 주요 구성요소와 역할을 중심으로 정리했습니다.
더 상세한 내용은 아래 포스팅에서 자세히 다루었습니다.


1. 자바 프로그램의 첫 관문 - 클래스 로더

2.Runtime Data Area 완전분석: 메모리 구조와 각 영역 역할

3. Garbage Collector 이해하기: 자동 메모리 관리와 성능 최적화

4.  Execution Engine 완전정복: 인터프리터와 JIT의 비밀


1. JVM이란 무엇인가?

JVM(Java Virtual Machine)은 자바 프로그램을 실행하기 위한 가상의 컴퓨터(Virtual Machine) 입니다.
자바 컴파일러(javac)가 .java 파일을 컴파일하면 .class 파일이 만들어지는데 이 안에는 바이트코드(Bytecode) 라는 JVM이 이해할 수 있는 명령어 집합이 담겨 있습니다.


바이트코드(Bytecode)란?

바이트코드는 CPU가 직접 이해할 수 있는 기계어가 아니라 JVM이 이해하고 해석할 수 있도록 설계된 플랫폼 독립적인 명령어 집합입니다.

즉 자바는 소스 코드를 한 번만 컴파일하면 JVM이 설치된 어떤 운영체제나 하드웨어에서도 동일한 바이트코드를 실행할 수 있습니다.

예를 들어

  • Windows용 JVM은 바이트코드를 Windows 시스템 콜에 맞게,
  • macOS용 JVM은 macOS 시스템 콜에 맞게 변환합니다.

이 덕분에 자바는 “Write Once, Run Anywhere” — 한 번 컴파일하면 어디서나 실행 가능한 언어입니다.


기계어(Machine Code)란?

기계어는 CPU가 직접 이해하고 실행할 수 있는 이진 명령어(0과 1의 조합) 입니다.
운영체제나 CPU 종류에 따라 명령어 구조가 다르기 때문에 보통 언어는 해당 환경에 맞는 기계어로 변환되어야만 실행될 수 있습니다.

하지만 자바는 JVM이라는 추상 계층을 두어 바이트코드를 JIT(Just-In-Time) 컴파일러를 통해 실행 시점에 각 환경의 기계어로 변환합니다.
이 덕분에 코드 이식성과 실행 효율성을 동시에 확보할 수 있죠.


2. JVM 내부 구조

아래 그림을 보면서  JVM 내부구조를 한번 알아봅시다.

JVM은 다음과 같은 주요 구성요소로 이루어져 있습니다.

  1. Class Loader Subsystem — .class 파일을 JVM 메모리에 로드
  2. Runtime Data Areas — 실행 중 데이터를 저장하는 메모리 구조
  3. Execution Engine — 바이트코드를 실제 기계어로 해석·실행
  4. Native Method Interface (JNI) — 네이티브 코드(C/C++) 호출
  5. Java Native Libraries — OS와 상호작용하는 표준 라이브러리

이제 각 구성요소가 실제로 어떻게 동작하는지 살펴봅시다.


3. Jvm은 어떻게 동작할까?

JVM은 프로그램 실행을 위해 다음과 같은 단계를 수행합니다.

  1. 코드를 메모리에 로드합니다.
  2. 코드를 확인합니다.
  3. 코드를 실행합니다.
  4. 런타임 환경을 제공합니다.

자바로 프로그램을 만들면 .java 소스는 컴파일러에 의해 .class 바이트코드로 변환됩니다.
이때 컴파일러는 JVM 외부에서 동작하고 그 결과로 만들어진 .class 파일은 JVM 내부로 전달됩니다.


 Class Loader Subsystem

자바 프로그램이 실행되면 가장 먼저 클래스 로더(Class Loader) 가 동작합니다.
클래스 로더는 말 그대로 클래스(.class 파일) 를 JVM 내부로 불러오는 역할을 합니다.
JVM은 필요한 클래스만 그때그때 동적으로 로딩하기 때문에, 애플리케이션이 커져도 모든 클래스를 한 번에 메모리에 올리지 않습니다.

.class 파일은 JVM의 Class Loader Subsystem으로 전달되어 다음 과정을 거칩니다.

  1. 클래스 로딩: .class 파일을 읽어 메모리에 로드
  2. 바이트코드 검증: 코드가 안전한지 검증
  3. 메모리 할당: 프로그램 실행에 필요한 메모리를 할당

Runtime data Area

Runtime Data Area는 JVM이 프로그램 실행 중 데이터를 저장하고 관리하는 메모리 공간입니다.
JVM은 이 영역을 다섯 가지로 나누어 각각의 역할에 맞게 데이터를 효율적으로 관리합니다.

1. Class(Method) Area

클래스 정의(바이트코드), 정적 변수, 상수 풀(Constant Pool), 메서드 코드 등이 저장되는 영역입니다.
즉, 클래스의 설계도와 같은 역할을 하는 메모리 공간입니다.


 왜 클래스를 미리 메모리에 올려둘까?

JVM이 클래스를 실행 도중에 필요할 때마다 디스크에서 계속 읽는다면 프로그램 실행 속도가 매우 느려질 것입니다.
따라서 Class Loader는 프로그램 실행 전에 필요한 클래스 정보를 미리 메모리에 올려두고
실행 중에는 이미 적재된 정보를 참조하여 빠르게 동작할 수 있도록 합니다.

또한 클래스에 대한 정보(정의, 메서드, 상수 등)는 프로그램 전체에서 공유되어야 하는 데이터이기 때문에
매번 새 객체가 생성될 때마다 같은 메서드 코드를 복사하는 것은 비효율적입니다.
그래서 JVM은 이 정보를 공유 영역(Class Area) 에 한 번만 저장해두고모든 인스턴스가 이를 참조(Reference) 하는 방식으로 동작합니다.

결과적으로 Class Loader는

  • 프로그램의 시작 속도를 높이고,
  • 중복 메모리 사용을 줄이며,
  • 클래스 재사용과 효율적인 참조를 가능하게 만드는 역할을 수행합니다.
왜 따로 분리했을까?
클래스에 대한 정보(정의, 메서드, 상수 등)는 프로그램 전체에서 공유되어야 하는 데이터이기 때문입니다.
매번 새 객체가 생길 때마다 같은 메서드 코드를 복사하는 건 비효율적이므로 JVM은 이 정보를 “공유 영역(Class Area)”에 한 번만 저장해두고 모든 인스턴스가 참조하게 만듭니다.

 


2. Heap Area

객체가 생성되는 영역입니다.
JVM이 클래스를 로드하면 Method Area와 Heap이 즉시 생성되며 모든 객체 인스턴스는 이곳에서 관리됩니다.

왜 따로 분리했을까?
Heap은 동적으로 생성되는 데이터(객체)를 관리해야 하기 때문입니다.
객체는 생성 시점과 소멸 시점이 일정하지 않으므로 스택처럼 고정된 크기의 메모리로는 관리가 어렵습니다.
따라서 JVM은 Heap을 별도의 영역으로 만들어 가비지 컬렉터(GC) 가 이 공간을 관리하도록 설계했습니다.

 

3. Stack Area

메서드 코드는 메서드 영역에 저장됩니다. 하지만 메서드 실행중에는 데이터와 결과를 저장하기 위해 더 많은 메모리가 필요합니다.
이 메모리는 java Stack영역에 할당됩니다.

Java Stack은 자바 메서드가 실행되는 영역입니다.  Java Stack에서는 메서드가 실행되는 별도의 프레임이 생성됩니다.
메서드가 호출될때마다 스택에 새로운 프레임이 생성됩니다. 메서드 호출이 완료되면 해당 메서드와 연관된 프레임이 삭제 됩니다.
JVM은 항상 각 메서드를 실행하기 위해 별ㄹ도의 스레드(또는 프로세스)를 생성합니다.

왜 따로 분리했을까?
스택은 메서드 호출이 끝나면 즉시 메모리를 반환할 수 있는 구조이기 때문입니다.
이는 함수 실행마다 빠르게 메모리를 할당하고 해제해야 하는 일시적 데이터에 최적화된 방식입니다.
이런 특징 덕분에 자바는 스택 오버플로우(StackOverflowError)를 제외하면
지역 변수 관리가 매우 빠르고 안정적입니다.

4. PC Register

PC(program counter) 레지스터는 현재 실행중인 JVM명령어의 메모리 주소를 포함하는 레지스터(메모리 영역)입니다.

 왜 따로 분리했을까?
자바는 멀티스레드 환경에서 동작하기 때문에 각 스레드가 어떤 명령을 실행 중인지 따로 기억해야 합니다.
즉 스레드 독립성을 보장하기 위한 구조입니다.

5. Native Method Stack

JNI(Java Native Interface)를 통해 실행되는 C/C++ 기반 네이티브 코드가 사용하는 스택입니다.
운영체제 수준의 라이브러리 호출이나 하드웨어 자원 접근 시 사용됩니다.

 왜 따로 분리했을까?
네이티브 메서드는 자바의 스택 구조나 메모리 모델과 다르게 동작합니다.
따라서 JVM 내부에서 완전히 분리된 공간을 두어 네이티브 코드 실행 중 발생할 수 있는 충돌이나 오염을 방지합니다.

 Java Native Library 예시

  • java.lang.System 클래스의 loadLibrary()
    → C로 작성된 네이티브 라이브러리를 로드할 때 사용됩니다.
    예: System.loadLibrary("sqlitejdbc");
  • sun.misc.Unsafe
    → JVM 내부 메모리에 직접 접근하거나, OS 수준 자원을 다루는 데 사용됩니다.
    (예: Direct Memory 접근, CAS 연산 등)
  • java.net 패키지 내부 네이티브 라이브러리
    → libnet.so (리눅스) / net.dll (윈도우)
    네트워크 소켓 통신을 위해 OS 네트워크 API를 직접 호출합니다.
  • java.io 관련 네이티브 코드
    → 파일 입출력 시 사용되는 libjavaio.so 등
    OS 파일 시스템 API와 직접 상호작용합니다.

JVM이 메모리를 5개 영역으로 나눈 이유는실행 속도와 안정성을 동시에 확보하기 위해서입니다.

  • Stack: 빠른 임시 데이터 처리
  • Heap: 동적 객체 관리
  • Method Area: 공유 코드 및 메타데이터 저장
  • PC Register: 스레드 독립 실행
  • Native Stack: 외부 코드와의 안전한 분리

4. Execution Engine — 바이트코드를 어떻게 실행하는가?

Execution Engine은 바이트코드를 실제 기계어로 변환하여 CPU에서 실행할 수 있도록 합니다.
이 엔진은 두 가지 주요 요소로 구성됩니다.

  • Interpreter : 바이트코드를 한 줄씩 해석하며 실행
  • JIT(Just-In-Time) Compiler : 자주 실행되는 코드를 기계어로 변환해 속도 향상
  • GC(Garbage Collector) - 사용하지 않는 객체 자동 해제

execution Engine은 이 세 가지가 조합되어 작동하며 자바 프로그램의 실행 속도를 최적화합니다.


 

Thank U, JVM

 

[JVM 완전정복 #4] Garbage Collector 이해하기: 자동 메모리 관리와 성능 최적화

 

bwj1207.tistory.com

 

 

[JVM 완전정복 #3] Runtime Data Area 완전분석: 메모리 구조와 각 영역 역할

 

bwj1207.tistory.com

 

 

 

[JVM 완전정복 #3] Runtime Data Area 완전분석: 메모리 구조와 각 영역 역할

 

bwj1207.tistory.com

 

 

 

[JVM 완전정복 #2] 클래스 로더 깊게보기: 클래스는 왜 메모리에 미리 올라갈까?

0. 들어가며지난 글에서는 JVM의 전체 구성요소와 각 역할을 살펴보았습니다.이번 글에서는 그중 클래스 로더(Class Loader)에 집중해 자바 프로그램 실행 시 클래스가 JVM 내부로 어떻게 올라가고 관

bwj1207.tistory.com

 

 

자바를 공부하면서 JVM 덕분에 우리가 얼마나 편하게 코드를 실행할 수 있는지 느꼈습니다.
CPU나 운영체제에 구애받지 않고 어디서나 프로그램을 실행할 수 있다는 점이 참 매력적이고, 메모리 관리와 성능 최적화까지 해주는 JVM이 정말 고맙게 느껴집니다.

 

저작자표시 비영리 (새창열림)

'자바' 카테고리의 다른 글

JIT 컴파일러와 일반 컴파일러의 차이를 알아보자  (0) 2025.11.14
Java Class 하위에 변수를 몇개까지 선언할 수 있을까?  (0) 2025.11.11
프로그래밍 언어의 시작 – 고급어와 저급어의 이해  (0) 2025.11.04
객체지향 프로그래밍(Object-Oriented Programming)  (1) 2025.10.28
프로그래밍이란 무엇일까?  (0) 2025.10.23
'자바' 카테고리의 다른 글
  • JIT 컴파일러와 일반 컴파일러의 차이를 알아보자
  • Java Class 하위에 변수를 몇개까지 선언할 수 있을까?
  • 프로그래밍 언어의 시작 – 고급어와 저급어의 이해
  • 객체지향 프로그래밍(Object-Oriented Programming)
깊은바다속꼬북이
깊은바다속꼬북이
  • 깊은바다속꼬북이
    CodeBlossom
    깊은바다속꼬북이
  • 전체
    오늘
    어제
    • 분류 전체보기 (53) N
      • 라이징 캠프 (4)
      • 객채지향 개발론 (3)
      • 스프링 (10) N
      • 네트워크 (2)
      • 자바 (16)
      • 자료구조 (3)
      • 운영체제 (0)
      • 데이터베이스 (4)
      • 디자인패턴 (7)
      • JSP (1)
      • 개발 알쓸신잡 (2)
      • 일반 교양 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    백엔드
    spring
    jit-compiler
    개발 교훈
    디자인 패턴
    자료구조
    mockito라이브러리
    JUnnit5
    싱글턴 패턴(Singleton Pattern)
    전략 패턴(Strategy Pattern)
    개발 철학
    스프링
    MySQL 옵티마이저
    junnit5프레임워크
    템플릿 메서드 패턴(Template Method Pattern)
    트랜잭션 전파레벨
    java data area
    GC
    java
    jvm 클래스 로더
    java 버전별 특징
    JVM
    어댑터 패턴(Adapter Pattern)
    한국어 검색
    디자인패턴
    자바 Socket 클래스
    객체지향
    프로그램밍 언어
    개발자 철학
    MySQL 파서
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
깊은바다속꼬북이
[JVM 완전정복 #1] JVM 구성요소 총정리: 가상 머신이 자바 코드를 실행하는 비밀
상단으로

티스토리툴바