JVM 동작 원리

zwundzwzig·2023년 9월 23일
1

Java

목록 보기
8/9
post-thumbnail

자바부터 다시 하자.
백기선님의 자바 스터디 2020

Java Virtual Machine

JVM은 자바로 만든 어플리케이션과 OS를 연결해주는, 물리적 머신과 유사한 머신을 소프트웨어로 연결해주는 가상머신이다. 내가 만든 자바 어플리케이션은 완전한 기계어가 아니기 때문에 OS가 읽어 실행시킬 수 있도록 하는 가상의 운영체계가 JVM이다.

즉, 자바 프로그램이 아무 CPU나 OS에서도 실행되도록 해주는 장치이다. 이로 인해 자바는 운영체제에 독립적인 언어라고 불린다.

동작방식은 다음과 같다.

  1. 개발자가 java 확장자로 짠 프로그램을 실행시킨다.
  2. JVM이 OS로부터 해당 프로그램이 실행되는데 필요한 메모리를 할당받는다.
  3. 컴파일러javac가 java 코드를 class 파일의 바이트코드로 컴파일한다.
    3-1. 이때 자바 코드를 컴파일 시켜주는 프로그램은 javac.exe이다. 이는 jdk에 포함돼 있고 javac 소스코드.java 명령어를 통해 JVM이 이해할 수 있는 바이트코드로 컴파일하는 역할을 지닌다.
    3-2. 그리고 그렇게 컴파일된 코드들은 java 명령어를 통해서 JVM 내부 클래스 로더에서 로딩되며 실행된다.
  4. 해당 class 파일들은 Class Loader에서 로딩-링킹-초기화 과정을 거친다.
  5. 이 과정에서 Execution Engine에서 class 파일은 해석-제거되고 런타임 영역에 배치된다.
  6. 런타임 영역에서 코드가 실행된다.

JVM 구성 요소

JVM 구성 요소는 다음과 같다. 출처

Class Loader

여기서는 개발자의 바이트코드 class 파일 및 class library를 런타임 시점에 Runtime Data Area 중 메서드 영역에 클래스를 동적 로딩하는 역할을 한다.

여기서 동적 로딩이란?

프로세스가 시작될 때 프로세스의 주소 공간 전체를 메모리에 올려놓는 것이 아니라, 필요한 루틴이 호출될 때 해당 루틴을 메모리에 적재하는 방식이다. 필요한 시점에만 올리니까 메모리를 더 효율적으로 사용할 수 있다.
런타임 동적 로딩 vs 로드타임 동적 로딩

클래스 로더는 Loading, Linking, Initializing 이렇게 세 가지 역할을 한다.

Loading

class 확장자의 바이트코드 파일을 Runtime Data Area 내 메서드 영역에 저장하는 일을 한다.

이때 로드된 클래스 및 그 부모 클래스 정보, 변수나 메서드 등의 정보를 함께 저장한다.

클래스 로더는 다음과 같은 계층 구조로 돼 있다.

  • Bootstrap Class Loader
    JVM을 기동할 때 함께 메모리에 올라간다.
    기본 자바 API를 로드한다.
    네이티브 코드로 구현돼 있다.
  • Extension Class Loader
    JDK가 추가적으로 제공하는 라이브러리이다.
    보안 등의 역할을 하며 주로 jre/lib/ext에 위치한다.
  • System Class Loader
    classpath에 설정된, 즉 개발자가 만든 어플리케이션의 $CLASSPATH 내의 class를 로드한다.

클래스 로더의 로딩 과정

  • 런타임 영역 중 메서드 영역에 특정 클래스가 로드돼 있는 지 확인하고 있는 경우 해당 클래스를 사용한다.
  • 없으면, 위임 원칙에 따라 System 클래스 로더부터 차례로 자신의 부모에게 요청한다.
  • 유일성의 원칙에 따라 상위 클래스 로더가 로드한 클래스와 같은 클래스를 하위 클래스에서 사용할 수 없다. 이때 클래스 로드별 네임스페이스에 보관된 FQCN을 기준으로 클래스를 확인한다.
  • 가시 범위 원칙에 따라 상위 클래스 로더가 로드한 클래스를 하위 클래스에서 사용할 수 있다.
  • 언로드 금지 원칙에 따라 로드된 클래스를 언로드할 수 없고, 필요하다면 삭제 후 다시 만들어야 한다.
  • 만약 모든 클래스 로더에서 찾지 못한다면, ClassNotFoundException을 발생시킨다.

Linking

Linking은 로드된 클래스 파일들을 검증하고, 사용할 수 있게 준비하는 과정을 의미합니다.

이 또한 Verification, Preparation, 그리고 Resolution이라는 세 가지 단계로 이루어져 있습니다.

  • Verification
    작성된 바이트코드가 자바 언어 명세를 잘 따랐는지, JVM 규격에 따른 검증된 컴파일러에서 컴파일됐는지 등 파일의 정확성을 확인한다. 클래스 로더 중 가장 오랜 시간이 소요된다.
  • Preparation
    메서드 테이블 같이 JVM에서 쓰이는 자료구조나 static storage을 위해 메모리를 할당하고, static field가 생성되고 초기화된다.
  • Resolution
    메서드 영역 내 런타임 상수 풀(run-time constant pool)에 있는 심볼릭 참조(symbolic reference)를 직접 참조(direct reference)로 대체한다.

Initialization

코드에 명시된 원래 값이 정적 변수에 할당되고, 정적 초기화 블록이 실행돼 static 필드들이 설정된 값으로 초기화한다.

이때 클래스의 위에서 아래로, 부모에서 자식으로 한 줄씩 실행된다.

Runtime Data Area


힙과 메서드 영역은 JVM이 시작될 때 생성되고, JVM이 종료될 때 소멸되며, JVM 하나에 힙과 메서드 영역이 각각 생성되고 모든 스레드는 이 두 영역을 공유한다.

클래스 단위에 속하는 런타임 상수 풀도 클래스가 생성/소멸될 때 함께 생성/소멸되며, 클래스마다 런타임 상수 풀도 하나씩 생성된다.

스레드 단위에 속하는 PC 레지스터, JVM 스택, 네이티브 메서드 스택 이 세가지는 영역은 스레드의 생명주기를 따른다.

Method Area

Class 정보, method 정보, static variable과 constant pool 정보를 저장한다.

jdk1.8부터 metaspace라는 이름으로 새롭게 도입됐는데, JVM에 할당된 메모리가 아닌 system의 native 메모리를 활용하도록 구현됐다. 이를 통해 Heap과의 의존성을 줄일 수 있게 됐고 OutOfMemoryError 발생 빈도롤 낮췄다. 물론 아직 GC 관리 대상이다.

JVM의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면 실제 물리 메모리 주소로 변환해 전달한다.

Heap Area

코드 실행을 위한 Java로 구성된 객체 및 JRE 클래스가 탑재된다.

문자열에 대한 정보를 가진 String Pool 뿐만이 아니라 실제 데이터를 가진 인스턴스, 배열 등이 저장이 됩니다.

출처

이 영역이 가득 차면 OutOfMemoryError를 발생될 수 있고, 객체의 참조가 정상적으로 해제되는 지 고려해야 하기 때문에 GC의 주 관리 대상이 된다.

사진에 나와있는 Permanent 영역은 jdk1.8부터 Native Method Stack으로 옮겨졌다.

Young과 Old 영역으로 구분되는데, Young은 말 그대로 최근에 생성된 객체들이 위치하는 영역이고 old 영역은 특정 threshold 이상의 기간 동안 살아남은 객체가 위치하는 영역이다.

Young 영역은 또다시 eden, s0과 s1으로 구분됩니다. s0과 s1은 survivor의 약자입니다. 객체가 새로 생성될 때 해당 객체는 eden 영역에 할당됩니다. 그리고 eden 영역이 더 이상 객체를 저장할 수 없는 경우 minor gc를 거쳐 s0 또는 s1의 영역으로 살아남은 객체를 이동시킵니다.

이렇게 Old 까지 살아남은 객체가 이동하는데, 이때 Old 영역이 가득차면 major gc가 실행되고, 이때 순간적으로 어플리케이션이 멈추는 stop the world 현상이 일어날 수도 있다.

중요한 설정으로 -Xmx와 -Xms가 있다. Xmx 설정을 통해 heap의 최대 크기를 제한할 수 있고 Xms 설정을 통해 heap의 초기(최소) 크기를 설정할 수 있다. Xmx 값을 시스템 메모리보다 크게 설정하면 swap이 발생하여 시스템 전체적인 성능이 악화되거나 시스템이 다운될 수 있다. 따라서 Xmx값을 시스템 메모리보다 작게 설정하도록 권고된다. 두 번째 권고 사항으로는 Xmx와 Xms 값을 동일하게 설정하는 것인데 이는 만약 두 설정값이 다른 경우 힙의 크기가 Xms에 도달했을 때 운영체제에게 추가 메모리를 요청함으로써 발생하는 오버헤드를 줄이기 위함이다. 프로덕션 환경에서 운영해 보면 결국 JVM이 사용하는 메모리는 Xms를 넘어 Xmx를 향해 증가하기 때문에 Xms와 Xmx를 동일한 값으로 설정하는 것은 성능적인 관점에서 효율적일 수 있다.

JVM Stack

스레드가 시작되면 메서드 호출을 저장하기 위해 별도의 런타임 스택이 생성된다. 모든 메서드 호출에 대해 하나의 엔트리가 생성되고 런타임 스택의 맨 위에 추가(push)되는데, 이러한 엔트리가 스택 프레임(Stack Frame)이다.

각 스택 프레임은 실행 중인 메서드가 속한 클래스의 로컬 변수 배열(Local Variable Array), 피연산자 스택(Operand Stack) 및 런타임 상수 풀에 대한 참조(Reference to Constant Pool)를 가지고 있다. 로컬 변수 배열은 메서드 안의 지역 변수들을 가지고 있다. 피연산자 스택은 메서드 내 연산을 위해서 바이트 코드 명령문들이 들어있는 공간이다. 상수 풀에 대한 참조는 Constant Pool 참조를 위한 공간이다.

이렇게 구성된 JVM Stack에는 메서드가 호출될 때마다 프레임(Frame)이 쌓이게 되고 메서드가 정상적으로 반환되거나 메서드 호출 중에 예외가 발생하면 프레임이 제거(pop)된다.

만약 이 영역에 너무 많은 데이터가 저장되는 경우 stack overflow error가 발생할 수 있다. 또한 JVM의 -Xss 옵션을 활용해서 stack의 최대 깊이를 제한할 수 있다.

PC Register

Program Counter을 저장하기 위해 사용되는 영역이다. 특정 thread의 실행 위치를 표시하므로 각각의 thread가 일을 하기 시작될 때 pc register가 생성된다.

만약 실행했던 메서드가 네이티브하다면 undefined가 기록되는데, 실행했던 메서드가 네이티브하지 않다면, PC Registers는 JVM에서 사용된 명령의 주소 값을 저장한다. 실행이 완료되면 PC 레지스터는 다음 명령의 주소로 업데이트된다.

Native Method Stacks

순수하게 자바로 구성된 코드만을 사용할 수 없는 시스템의 자원이나 API가 존재하는데, 이러한 메서드들을 Native Method라고 하며, Native Method Stacks는 자바로 작성되지 않은 메서드 정보를 저장하는 영역이다.

Java Native Interface를 통해 호출하는 네이티브 언어를 수행한다.

각각의 스레드들이 생성되면 Native Method Stacks도 스레드 별로 생성된다. 앞의 JVM Stack Area처럼 Native Method가 실행되면 Stack에 해당 메서드가 쌓인다.

Execution Engine

Execution engine은 JNI를 활용해 운영체제의 함수를 호출하는 역할이다.

Interpreter

두 가지 방식이 존재하는데, 첫 번째로 바이트코드의 기본적인 방식인 인터프리터이다.

이는 한줄씩 바이트코드를 기계에로 해석하고 실행한다. 자바스크립트와 비슷하다고 볼 수 있다.
하지만 이는 규모가 큰 프로젝트에서 속도적으로 한계를 띄고 있다.

JIT Compiler

프로젝트 전체의 바이트코드를 한번에 컴파일하고 네이티브 코드로 변경한 후 캐시에 저장한다. 이때 어플리케이션이 시작한 지 얼마 안 돼 코드가 충분히 캐싱 및 최적화되지 않아 응답 지연이 발생할 수도 있다. JVM warmup을 통해 해결될 수도 있다.

동작 과정

  • Intermediate Representation Generator
    바이트 코드를 중간 단계의 표현인 IR(Intermediate Representation)로 변환
  • Code Optimizer
    생성된 중간 코드를 최적화
  • Target Code Generator
    네이티브 코드 생성
  • Profiler
    '핫스팟'(ex. 하나의 메서드를 여러 번 호출하는 인스턴스)과 같은 성능 병목 현상을 찾는 특수 구성 요소

Garbage Collection

나중에 볼 동영상

참고자료

profile
개발이란?

0개의 댓글