[Java] JVM(Java Virtual Machine): 자바 가상 머신의 구조와 동작 원리, 객체 생성 흐름

JVM이란 무엇인가?

JVM(Java Virtual Machine)은 자바 프로그램을 실행하기 위한 가상 머신(소프트웨어)입니다. 이는 자바 코드가 다양한 운영체제에서 동일하게 실행될 수 있도록 하는 핵심 요소로, 자바 프로그램이 JVM이 설치된 어떤 환경에서도 실행할 수 있는 플랫폼 독립성을 제공합니다.

 

자바 코드의 실행 과정

https://www.viralpatel.net/java-virtual-machine-an-inside-story/

1. 자바 소스 코드 작성
개발자는 .java 확장자를 가진 자바 소스 파일을 작성합니다.

 

2. 컴파일 단계: javac(java.exe)를 통한 컴파일
자바 소스 파일은 JDK(Java Development Kit)에 포함된 컴파일러인 javac를 사용하여 컴파일됩니다.
이 과정에서 .java파일은 .class 확장자를 가진 바이트코드 파일로 변환됩니다.

 

3. 클래스 로딩: JVM의 클래스 로더
컴파일된 .class파일은 JVM의 클래스 로더에 의해 메모리에 로드됩니다. 클래스 로딩은 다음 세 단계로 이루어집니다.

      • 로딩(Loading): 클래스 파일을 찾아서 메모리에 로드합니다.
      • 링킹(Linking): 클래스의 구조를 검증하고, 필요한 참조를 해결합니다. 이 과정을 통해 클래스는 실행 가능한 상태로 준비됩니다.
      • 초기화(Initialization): 클래스 static 초기화 블록과 static 변수들을 초기화합니다. 이 단계는 클래스가 처음으로 사용될 때 한 번만 실행됩니다.

클래스 로더는 부모 계층 구조를 가지며, 일반적으로 세 가지 기본 클래스 로더가 있습니다.

  • 부트스트랩 클래스 로더: JVM의 핵심 클래스를 로드합니다.
  • 확장 클래스 로더: JDK의 확장 디렉토리에 있는 클래스를 로드합니다.
  • 시스템 클래스 로더: 애플리케이션 클래스패스에 지정된 클래스를 로드합니다.

이러한 구조를 통해 JVM은 필요한 클래스를 동적으로 로드하여 실행 준비를 합니다.

 

4. 실행: main 메서드 호출

JVM은 프로그램의 진입점인 main 메서드를 호출하여 프로그램을 실행합니다. 이 과정에서 바이트코드는 인터프리터 또는 JIT(Just-In-Time) 컴파일러에 의해 기계어로 변환되어 실행됩니다.

  • 인터프리터(Interpreter): 바이트코드를 한 줄 씩 해석하여 실행합니다.
  • JIT 컴파일러: 자주 실행되는 바이트코드 블록을 네이티브 코드로 컴파일하여 성능을 향상시킵니다.

 

JDK, JRE, JVM의 관계

.java.class로 컴파일 시 JDK에 포함된 컴파일러 javac를 이용합니다. 그럼 JDK와 JRE는 무엇인지 알아보겠습니다.

 

JDK, JRE, JVM은 자바 애플리케이션의 개발과 실행을 위한 핵심 구성 요소입니다. JDK는 개발 도구를 포함하여 애플리케이션을 개발할 수 있게 하며, JRE는 애플리케이션을 실행할 수 있는 환경을 제공합니다. JVM은 바이트코드를 실행하여 자바의 플랫폼 독립성을 실현합니다.

JDK
├── JRE
│   ├── JVM
│   └── 클래스 라이브러리
└── 개발 도구 (컴파일러, 디버거 등)

 

1. JDK (Java Development Kit): 자바 개발 도구 모음

JDK는 자바 애플리케이션을 개발하기 위한 도구 모음으로, JRE와 개발 도구를 포함하고 있습니다.

  • JRE: 자바 애플리케이션을 실행하기 위한 환경입니다.
  • 컴파일러(javac): 자바 소스 코드를 바이트코드로 컴파일합니다.
  • 디버거(jdb): 자바 애플리케이션의 디버깅을 지원합니다.
  • 문서 생성기(javadoc): 자바 소스 코드에서 API 문서를 생성합니다.
  • 패키징 도구(jar): 클래스 파일과 리소스를 JAR 파일로 패키징합니다.

2. JRE (Java Runtime Environment): 자바 애플리케이션 실행 환경

JRE는 자바 애플리케이션을 실행하기 위한 환경으로, JVM과 자바 클래스 라이브러리로 구성되어 있습니다. JRE는 다음과 같은 구성 요소를 포함합니다

  • JVM: 자바 바이트코드를 실행하는 가상 머신입니다.
  • 클래스 라이브러리: 자바 애플리케이션에서 사용할 수 있는 표준 클래스와 API를 제공합니다.
  • 지원 파일: 애플리케이션 실행에 필요한 구성 파일과 리소스를 포함합니다.

 

JVM 메모리 구조

JVM은 자바 프로그램을 실행하기 위해 다양한 메모리 영역을 사용합니다. 클래스 로딩 과정에서 각 메모리 영역에 어떻게 적재되는지 확인해보겠습니다.

https://www.devkuma.com/docs/jvm/memory-structure/

  1. 메서드 영역 (method/static Area)
    • 모든 스레드가 공유하는 영역으로 클래스 로딩 시 메모리에 로드되는 정보들이 이 영역에 저장됩니다.
    • JVM은 특정 클래스가 사용되면 해당 클래스 즉, 자바 바이트 코드인 클래스 파일(*.class)를 읽어 들인 클래스와 인터페이스에 대한 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(static 변수), 생성자와 메소드를 메소드 영역에 저장합니다.
    • 메서드 영역에 로드되면 JVM이 종료될 때가지 할당되어 있습니다.
  2. 힙 영역 (Heap Area)
    • 객체와 배열을 저장하는 곳입니다.
    • 메서드 영역에 로드된 클래스가 new 키워드로 동적으로 생성된 객체들이 저장됩니다.
    • Garbage Collector가 참조되지 않는 메모리를 확인하고 제거하는 영역입니다.
  3. 스택 영역 (Stack Area)
    • 메서드 호출 시 생성되는 프레임을 저장합니다. 메서드 호출 시 생성되는 지역 변수, 매개변수리턴주소가 생성됩니다.
    • 각 스레드마다 생성되며, 각 메소드 호출마다 새로운 스택 프레임이 생성되며, 메소드가 끝나면 해당 프레임이 사라집니다.
    • 인스턴스가 힙 영역에서 생성될 시, 이 영역에서는 생성한 객체의 참조 변수가 저장됩니다.
  4. PC(Program Counter) 레지스터
    • 현재 실행 중인 명령어의 주소를 저장합니다.
    • 각 스레드마다 독립적으로 존재하며, JVM이 명령어를 순차적으로 실행하는데 사용됩니다.
  5. 네이티브 메서드 스택
    • 자바가 아닌 네이티브 코드를 위한 메모리 영역으로 JNI(Java Native Interface)를 통해 호출되는 메서드의 정보를 갖고 있습니다.

 

객체 생성 흐름

  1. 클래스 로딩: 클래스가 처음 호출되면 메소드 영역에 클래스 정보가 로드됩니다.
  2. 메모리 할당: 객체를 생성하면, JVM은 힙 영역에서 메모리를 할당하고 해당 객체의 주소를 반환합니다.
  3. 참조 변수에 할당: 반환된 객체의 주소는 스택 영역의 참조변수에 저장됩니다.
  4. 참조를 통해 객체 접근: 참조변수를 통해 객체의 필드와 메소드를 사용할 수 있습니다.
  5. 메모리 제거: 스택 영역의 변수가 힙 영역의 인스턴스를 참조하고 있다가 더 이상 참조하지 않게 되면, JVM의 GC(Garbage Collection)을 통해 인스턴스는 제거됩니다.

 

가비지 컬렉션(Garbage Collection)

가비지 컬렉션(Garbage Collection)은 JVM(Java Virtual Machine)의 핵심 구성 요소 중 하나로, JVM 내부에 포함되어 있습니다.

자바에서 객체는 힙 메모리에 생성되며, 더 이상 참조되지 않는 객체는 가비지로 간주됩니다. 가비지 컬렉션은 이러한 불필요한 객체를 자동으로 식별하고 제거하여 메모리를 회수하는 과정입니다. 이러한 자동 메모리 관리는 개발자가 수동으로 메모리를 해제해야 하는 언어(C/C++ 등)에서 발생할 수 있는 메모리 누수나 오류를 방지합니다.