JVM 이야기

soyul·2025년 4월 11일

JVM

목록 보기
1/1

0. JVM이란 무엇인가

0.1. JVM이란

  • Java Virtual Machine
    • 자바를 실행하기 위한 가상 머신
  • OS에 종속받지 않고 CPU가 자바를 인식, 실행할 수 있게 해준다
    • Java언어를 OS가 인식할 수 있는 기계어로 컴파일 할 필요 없이 JVM이 인식할 수 있는 Java bytecode로 변환한다

0.2 Hotspot 이란

  • 오라클 JDK와 Open JDK 의 기본 가상 머신이자 가장 널리 사용되는 자바 가상 머신
  • 핫 코드 감지 기능
    • 컴파일했을 때 효과를 가장 크게 볼 수 있는 코드 영역을 런타임에 알아내어 JIT 컴파일러에 알려준다
    • JIT 컴파일러가 해당 코드를 메서드 단위로 컴파일
    • 자주 호출되거나 시간이 많이 드는 메서드가 있다면 JIT 컴파일을 수행해 스택을 치환

1. JVM 동작

  • 동작 과정
    • 컴파일
    • 클래스 로딩
    • 메모리 할당
    • 바이트 코드 실행

1.1 컴파일

  • 자바 소스를 실행하기 위해선 자바 컴파일러 javac를 이용해 컴파일링 필요
  • 자바 소스 코드를 바이트 코드 형식으로 만들어진 .class 파일로 수정한다
  • 아래 표의 규칙을 따라 .class파일이 만들어진다
    • ex. 모든 클래스 파일은 매직 넘버로 시작(이 파일이 클래스 파일임을 나타낸다)
  • HelloWorld 클래스를 javac로 컴파일하면?
    • javac HelloWorld.java → HelloWorld.class 생성
public class HelloWorld {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello World");
        }
    }
}

  • 생성자가 자동으로 추가 되므로 생성자랑 main 메서드 두 개 생성
  • aload_0 : 생성자에서 this 레퍼런스를 스택 상단에 올려놓는다
  • invokespecial : 슈퍼 생성자 호출, 객체 생성 등 특정 작업을 담당하는 인스턴스 메서드 실행
  • iconst_0 : 정수형 상수 0을 평가 스택에 push

1.2 클래스 로딩

  • JVM이 실행 시점에 클래스들을 메모리로 동적으로 불러오고 준비하는 과정
  • 클래스 로더
    • 컴파일된 바이트 코드를 읽고 클래스를 메모리에 올린다
    • 자바 클래스들을 한 번에 메모리에 올리지 않고, 애플리케이션에 의해 필요할 때 동적으로 메모리에 올린다
    • Runtime시 동적으로 클래스를 로드하여 클래스를 처음으로 참조할 때 해당 클래스를 로드
  • HelloWorld 명령을 내리면
    • OS는 가상 머신 프로세스(자바 바이너리) 구동
    • 자바 가상 환경 구성 및 스탠 머신 초기화
    • 유저가 작성한 HelloWorld 클래스 파일 실행
    • 애플리케이션의 진입점인 main() 메서드를 호출하려면, 가상 머신 실행이 시작되기 전에 클래스를 로드 해야한다 -> 클래스로딩

1.2.1 Loading

  • 클래스를 읽어오는 과정
  • 클래스 로더에 의해 메모리에 로드된 .class파일을 읽고, 분석하여 데이터를 런타임 데이터 영역 저장
  • 클래스, 인터페이스, 메서드, 필드 등에 대한 심볼릭 레퍼런스라는 값을 런타임 상수 풀에 복사

1.2.2 Linking

  • 참조와 실제 메모리 주소 값을 연결하는 과정
  • 3 단계
    • Verify : 로드된 .class 파일이 유효한 지 확인하는 과정
      • .class 파일이 JVM의 명세대로 구현되지 않은 경우 에러
    • Prepare : 클래스나 인터페이스에 필요한 메모리를 할당
    • Resolve : Loading단계에서 복사한 심볼릭 레퍼런스(symbolic reference) 값을 다이렉트 레퍼런스(direct reference)라는 메모리 주소 값으로 변경
      • (e.g. Book b = new Book(); // 참조변수 b가 Heap에 저장된 Book의 메모리 주소를 참조하도록 연결하는 과정)

1.2.3 Initialization

  • java코드에서 선언한 static 변수와 static 메서드를 지정한 값들로 초기화 및 초기화 메서드를 실행시켜 주는 과정
  • 앞선 단계에서 참조에 대한 값을 실제 메모리 주소로 변경하였으므로, 지정한 값들로 초기화

1.2.4 클래스 로더

  • Bootstrap
    • JDK 내부 클래스 로딩 (java.lang.* 등)
  • Extension
    • 주로 자바의 표준 라이브러리와 확장 라이브러리를 로드
  • Application
    • Classpath에 있는 모든 클래스 (.class, .jar) 로딩

3. 런타임 데이터 영역

  • JVM이 프로그램을 수행하기 위해 OS로부터 할당 받는 메모리 영역
  • 공유 영역 : Heap, Method 영역
  • 스레드 별로 할당되는 영역 : PC Register, Stack, Native Method Stack

3.1 Heap

  • new 키워드로 생성된 객체와 배열이 저장되는 공간
  • GC(Garbage Collector)의 대상
  • JVM 성능 튜닝 시 가장 중요하게 관리되는 영역

3.2 Method Area

  • 클래스 로더가 로드한 클래스, 인터페이스, static 변수, 메서드 메타정보 등 저장

3.3 PC 레지스터

  • 현재 스레드가 실행 중인 바이트코드 명령어의 주소를 저장
  • JVM 내부에서의 "명령어 포인터" 역할

3.4 JVM 스택

  • 스레드별 메서드 호출 정보 저장, 지역 변수 관리
  • 메서드가 호출될 때마다 Stack Frame이 생성 및 종료

3.4 Native Method Stack

  • C/C++ 같은 네이티브 메서드가 실행될 때 사용

4. JIT

  • Just In Time 컴파일
    • 프로그램을 실제 실행하는 시점에 기계어로 번역
    • 그때 그때 하는 컴파일
  • 자바 프로그램은 바이트코드 인터프리터가 가상화한 스택 머신에서 명령어를 실행하며 시작
  • CPU를 추상화한 구조라서 다른 플랫폼에서도 클래스 파일을 문제 없이 실행 가능하지만, 프로그램 성능을 최대로 내려면 네이티브 기능을 활용해 CPU 에서 직접 프로그램을 실행해야만 함
  • 이를 위해 프로그램 단위(메서드와 루프)를 인터프리티드 바이트코드에서 네이티브 코드로 컴파일 -> JIT
  • 인터프리티드 모드로 실행하는 동안 애플리케이션을 모니터링하면서 가장 자주 실행되는 코드를 발견해 JIT를 수행
  • 특정 메서드가 한계치를 넘어가면 프로파일러가 특정 코드 섹션을 컴파일, 최적화
  • 장점
    • 컴파일러가 해석 단계에서 수집한 추적 정보를 근거로 최적화를 결정
    • 상황별로 수집한 다양한 정보를 토대로 올바른 방향으로 최적화가 가능

0개의 댓글