본문 바로가기

자바

JVM, JRE, JDK

JDK, JRE, JVM

JDK, JRE, JVM의 구조

JDK

JDK는 자바 개발도구(Java Development Kit)의 약자이다.

우리가 일반적으로 자바로 개발하기 위해 설치하는게 바로 JDK이다.

JDK는 JRE, JVM을 포함하고 있기에 JDK를 설치하면 자동으로 JRE, JVM이 설치되어 이를 사용할 수 있다.

JDK에는 우리가 작성한 자바 문법을 컴퓨터가 이해할 수 있게 바꿔주는 자바 컴파일러(javac, java compiler)등을 포함하고 있다.

jar, war와 같은 형태로 배포하여 JRE에서 이를 실행한다.

JRE

JRE는 자바 실행환경(Java Runtime Environment)의 약자이다.

자바 언어로 작성된 프로그램을 실행하는 사용자들이 설치하는게 바로 JRE이다.

JRE는 JVM이 자바 프로그램을 동작시킬 때 필요한 자바 클래스 라이브러리 파일, 자바 클래스 로더를 포함하고 있다.

JRE 그 자체로 특별한 기능을 한다기보다 JVM이 원활하게 작동할 수 있도록 환경을 마련해주는 역할을 한다.

JVM

JVM은 자바 가상머신(Java Virtual Machine)의 약자이다.

JVM은 직역하자면 자바를 실행하기 위한 기계라고 할 수 있으며 작성된 자바 애플리케이션을 실행하기 위한 가상 기계이다.

자바 애플리케이션과 일반 애플리케이션의 실행에 대해 다음 그림에서 살펴보자.

일반 애플리케이션의 경우, OS만 거치고 컴퓨터(하드웨어)로 바로 전달된다.

하지만, 자바 애플리케이션은 JVM을 한 번 더 거쳐 진행하는 모습을 보여주고 있다.

자바 애플리케이션은 JVM에서는 중간 코드인 바이트 코드를 다루기 때문에 한번 더 컴퓨터(하드웨어)에 맞는 고급 언어가 되기 위해 실행 시에 해석(Interpret) 단계를 거쳐야해서 속도가 더 느리다고 볼 수 있지만 JIT 컴파일러와 향상된 최적화 기술이 적용되어 속도의 격차를 많이 줄였다.

그에 비해, 자바 애플리케이션은 JVM을 한 번 거치기에 자바 애플리케이션 자체는 OS에 독립적으로 실행할 수 있다라는 장점이 존재한다.

하지만 JVM은 OS와 직접적으로 상호작용하기 때문에 OS에 종속적이라는 사실을 꼭 알아둬야한다.

이제 자바를 사용하기 위한 JDK, JRE, JVM에 대해 알아보았으니 JVM에 대해서 알아보도록 하자.

JVM

JVM에 대해 알아보기 전에 자바프로그램이 어떻게 실행되는지 알아보자.

자바애플리케이션 실행과정

  1. 자바 애플리케이션이 실행되면, JVM은 OS로부터 애플리케이션이 필요로 하는 메모리를 할당받는다.
    JVM은 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

2.자바 컴파일러(javac)가 자바 소스코드를(.java)를 읽어 자바 바이트코드(.class)로 변환시킨다.

3.Class Loader를 통해 class파일들을 읽어 JVM으로 로딩한다.

4.로딩된 class파일들은 Execution Engine에 의해 해석된다.

5.해석된 바이트코드는 Runtime Data Areas에 배치되어 실제 동작을 한다.

이러한 과정속에서 JVM은 필요에 따라 Thread Syncrhonization과 GC같은 작업을 수행한다.

JVM 구조

Class Loader

  • Loading
    • Bootstrap Class Loader
      • 이 로더는 JVM을 기동할 때 생성되며, 자바 API들을 로드하는 역할을 한다.
    • Extention Class Loader
      • 이 로더는 기본 자바 API를 제외한 확장 클래스들을 로드하는 역할을 한다.(e.g. JDBC.jar)
    • System Class Loader
      • 부트스트랩 클래스 로더와 익스텐션 클래스 로더가 JVM 자체의 구성 요소들을 로드하는 것이라 한다면, 시스템 클래스 로더는 애플리케이션의 클래스들을 로드하는 역할을 한다.
  • Linking
    • Verifying(검증)
      • 읽어 들인 클래스가 자바 언어 명세 및 JVM에 명시된 대로 잘 구성되어 있는지 검사한다.
      • 클래스 로드 과정 중 가장 복잡하고 시간이 오래걸린다.
    • Preparing(준비)
      • 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메서드, 인터페이스들을 나타내는 데이터 구조를 준비한다.
    • Resolving(분석)
      • 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.
      • GilSSang gilSSang = new GilSSang(); 
        // new GilSSang() 부분이 실제 레퍼런스를 가르키지 않는데, Resolving 시점에 이를 실제 힙에 있는 인스턴스로 가르키게 해준다.
  • Initialization
    • 클래스 변수들을 적절한 값으로 초기화하는 역할을 한다. (static 필드 초기화)

Execution Engine

  • Interpreter
    • 자바 바이트 코드를 기계가 이해할 수 있도록 한 줄 단위로 읽어서 네이티브 코드로 바꾸는 작업을 진행한다.
    • 한 줄마다 컴파일하여 네이티브 코드로 변환하는 작업을 수행하기 때문에 느리다.
  • JIT(Just-In-Time) Compiler
    • 인터프리터의 단점을 보완하기 위해 도입되었다.
    • 인터프리터 방식으로 실행하다가 <적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고 더 이상 인터프리터를 이용하여 진행하지 않고 네이티브 코드로 직접 실행한다.
    • 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행한다.
    • JIT Comiler는 시간이 오래걸린다.
  • Garbage Collector
    • 더 이상 참조되지 않는 객체들을 정리하는 GC를 수행한다.

Runtime Data Area

  • PC Register
    • Thread가 시작될 때 생성되고 생성될 때마다 생성되는 공간으로 Thread마다 하나씩 존재한다.
    • Thread가 실행되는 부분의 주소와 명령을 저장하고 있다.
  • JVM Stack
    • 프로그램 실행과정에서 임시로 할당되었다가(push) 메소드를 빠져나가면 소멸되는(pop) 특성의 데이터를 저장하기 위한 영역이다.
    • 메소드 호출시마다 각각의 스택 프레임이 쌓이고(push) 수행 종료 후 삭제(pop)된다.
    • 각종 형태의 변수나 임시 데이터, 메소드의 정보, 파라미터, 지역 변수 등을 저장한다.
  • Native Method Stack
    • 바이트 코드가 아니라 실제로 실행할 수 있는 네이티브 코드로 작성된 프로그램을 실행시키는 영역이다.
    • JAVA Native Interface를 통해 바이트코드로 전환하여 저장하게된다.
  • Method Area
    • 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다.
    • 프로그램의 흐름을 구성하는 바이트 코드들을 저장하는 메모리 공간이다.
    • Runtime Constant Pool이라는 별도의 관리 영역이 존재해 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 한다.
    • Field Information(멤버변수 이름, 데이터 타입, 접근제어자에 대한 정보), Method Information(메소드의 이름, 리턴타입, 매개변수, 접근제어자에 대한 정보), Type Information(class인지 interface인지의 여부 저장, Type의 속성, 전체 이름, super class의 전체이름의 정보 저장)을 저장한다.
  • Heap
    • new 키워드로 생성되는 객체와 배열들을 저장하는 가상 메모리 공간이다.
    • Young Generation
      • Eden 1개와 Survivor 2개로 이루어진다.
      • 새롭게 생성된 객체가 할당되는 영역이다.
    • Old Generation
      • 접근 불가능 상태(Unreachable)로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다.
      • 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC가 적게 발생한다.

Garbage Collector

Garbage Collector는 기본적으로 2가지 공통적인 단계를 따른다.

  1. Stop The World
    • Grabage Collection을 실행하기 위해 JVM은 애플리케이션의 실행을 멈춘다.
    • GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업이 중단되고 GC가 완료되면 작업이 재개된다.
  2. Mark And Sweep
    • 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업을 수행한다.(Mark)
    • 사용되지 않는 메모리를 해제한다.(Sweep)

Minor GC

Young Generation 부분의 GC를 진행한다.

동작 과정

  1. 새로 생성된 객체가 Eden 영역에 할당된다.
  2. 객체가 계속 생성되어 Eden 영역이 꽉차게 되면 Minor GC가 실행된다.
    1. Eden 영역에서 사용되지 않는 객체의 메모리를 식별한다.
    2. 사용되지 않는 객체의 메모리를 해제시킨다.
    3. Eden 영역에서 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
  3. 1~2번의 과정이 반복되다가 Suvivor 영역이 가득 차게 되면 해당 Survivor 영역에서 살아남은 객체를 다른 Survivor 영역으로 이동시킨다. (이 때, 꽉 찬 Survivor은 다른 Survivor로 이동시키면서 다른 Survivor 영역은 빈 상태가 된다.)
  4. 이러한 과정을 반복하여 살아남은 객체가 Old 영역으로 이동한다.

Major GC

객체들이 Young Generation에서 Old Generation으로 이동됨으로써 Old 영역의 메모리가 부족해지면 Old Generation에 대해 GC를 진행한다.

Young 영역에 비해 크기가 크고 Young 영역을 참조할 수도 있기에 시간이 Minor GC에 비해 오래걸린다.

Minor GC에 비해 애플리케이션의 성능에 크게 영향을 준다.

이를 간단하게 표로 정리하면 다음과 같다.

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

JAVA Collection  (0) 2021.10.15
JAVA Lambda, Functional Interface  (0) 2021.10.14
JAVA Exception  (0) 2021.10.13
Java Optional  (1) 2021.10.04
Java Stream  (1) 2021.10.03