본문 바로가기
Language/Java

[Java] JVM Architecture (ClassLoader, Runtime Data Areas, Execution Engine) 정리

by 드럼치는 코린이 2021. 8. 31.
728x90
반응형

앞서 JDK의 대해서 공부하면서 JRE를 통해 JVM이 구동되는 것을 배웠다.
오늘은 java의 핵심 프로그램 JVM(Java Virtual Machine)의 구조의 대해서 공부해보려고 한다.

모든 Java 개발자들은 바이트코드가 JRE(Java Runtime Environment)에 의해 실행된다는 것은 알고 있다.
하지만 많은 사람들은 JRE가 바이트 코드를 분석하고 코드를 해석하고 실행하는 JVM(Java Virtual Machine)을 구현하는 사실은 잘 모른다. 개발자로서 JVM의 구조의 대해 아는 것은 매우 중요한데, 그 이유는 JVM을 통해 코드를 보다 효율적으로 작성할 수 있기 때문이다. 그렇다면 어떤 구조로 작동하는지 JVM의 다양한 구성 요소를 알아보도록 하자.


JVM이란?

가상 머신은 물리적 머신을 구현한 소프트웨어다. Java는 자바는 애초에 WORA(Write Once Run Anywhere, 한번 쓰고 언제어디서나 실행 가능) 컨셉으로 개발된 언어이며 가상머신으로 운영할 수 있는 언어다. 컴파일러는 인터프리터 방식으로 .Java 소스파일을 .class 파일로 컴파일한 다음 해당 .class 파일을 ClassLoader를 통해 JVM에게 건네주게 되면 JVM이 .class 파일을 메모리에 로드하며 실행할 수 있다.

JVM Architecture Diagram


JVM은 어떻게 작동하는가?

위의 그림과 같이 JVM은 3가지 서브 시스템으로 구분할 수 있다.

  1. 클래스 로더 서브 시스템 (Class Loader Sub System)
  2. 실행 데이터 영역 (Runtime Data Areas)
  3. 실행 엔진 (Execution Engine)

위에 3가지 서브 시스템에 대해서 정리를 해보자.


1. 클래스 로더 서브 시스템 (Class Loader Sub System)

자바의 동적 클래스 로딩 기능은 클래스 로더 서브시스템에 의해서 처리된다. 컴파일 타임이 아닌 클래스를 처음 참조하는 런타임을 할때 클래스파일을 로딩, 연결, 초기화 작업이 이루어진다.

1.1 로딩 (Loading)

Bootstrap, Extension, Application - 이 컴포넌트들에 의해 클래스들이 로드된다. 클래스 로더들이면서 이 3가지 클래스로더에 의해 클래스가 로드된다. 이 3가지 클래스 로더들은 모두 상속관계로 정의되어 있으며 delegate(위임) 방식으로 작업을 진행하게 된다.

  • Bootstrap ClassLoader

    - jre의 lib폴더에 있는 rt.jar 파일을 뒤져 기본 자바 API 라이브러리를 로드하고, 가장 최우선으로 로드하게 된다.

  • Extension ClassLoader

    - Bootstrap ClassLoader의 child 이며, Platform ClassLoader라고 부르기도 한다.
    - jre의 lib 폴더에 있는 ext 폴더에 있는 모든 확장 코어 클래스파일들을 로드한다.
    - Extension 클래스 로더는 jdk 확장 디렉토리(JAVA_HOME/lib/ext 디렉토리 혹은 java.ext.dirs 에 저장된 경로)에서 로드된다.

  • Application ClassLoader

    - Extension ClassLoader의 child이며, 시스템 클래스로더(System ClassLoader)라고 부르기도 한다.
    - 어플리케이션 레벨에 있는 클래스들을 로드한다. 즉, 사용자가 직접 정의한 클래스파일들을 로드한다.
    - Classpath 환경변수에 있는 클래스 파일이나 -classpath 또는 -cp 명령어 옵션이 있는 파일들을 로드한다.

1.2 연결 (Linking)

  • 검증 (verify)

    JVM에서 사용이 가능한 형태인지를 검증하는 작업을 수행한다. 검증이 실패할 경우 검증오류를 내보내게 된다.

  • 준비 (prepare)

    모든 정적변수의 메모리가 할당되며 기본 default 값으로 할당된다.

  • 해석 (resolve)

    심볼릭 메모리(명확하게 정의되지 않은 메모리) 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다. 즉 실제 메모리 주소 값으로 변경해 주는 작업을 의미.

1.3 초기화 (initialize)

클래스 로딩의 마지막 단계로써 모든 정적(static) 변수가 자바 코드에 명시된 값으로 초기화되며 정적블록이 실행된다.


2. 실행 데이터 영역 (Runtime Data Areas)

실행 데이터 영역은 5가지 구성 요소로 구분된다.

  1. 메서드 영역 (Method Area)

    - 모든 클래스 수준(클래스명, 부모클래스명, 메소드, 변수)의 데이터가 저장된다.
    - 저장된 데이터는 공유자원이며 JVM당 하나의 영역만 존재한다.

  2. 힙 영역 (Heap Area)

    - 모든 인스턴스와 객체(클래스, 배열 등)와 같은 참조형 데이터 타입이 저장되는 공간이다.
    - JVM당 하나의 영역만 존재하고 공유자원이다.
    - 위의 메서드 영역에 있는 데이터와 마찬가지로 멀티스레드에서 접근이 가능한 공유자원이기 때문에 스레드에 안전하지 않다.
    - 더이상 객체가 사용되지 않는다면 Garbage collection의 청소 대상이 된다.

  3. 스택 영역 (Stack Area)

    - 각각의 스레드마다 개별의 스택영역이 존재한다.
    - 메서드 콜스택이 메서드가 호출될 때마다 스택에 스택 프레임이라는 스택 메모리에 쌓이게 된다.
    - 모든 지역변수, 매개변수, 리턴값, 참조변수 등등 기본형 타입이 스택 메모리에 저장된다.
    - 스택 영역은 공유자원이 아니기 때문에 스레드에 안전하다.
    - 스택 프레임은 3가지 하위 항목으로 나누어진다.

    1) 지역변수 배열 (Local Variable Array)

       메소드가 얼마나 많은 지역변수를 포함하는 가와 해당하는 값에 대한 정보가 저장된다.

    2) 피연산자 스택 (Operand Stack)

       중간연산이 필요로 할 때, 연산작업을 수행하기 위한 작업공간 역할을 한다.

    3) 프레임 데이터 (Frame Data)

      Frame data 는 Constant pool resolution, Exception Dispatch, Normal method return 등의 정보를 가지고 있는 곳이다. 예외가 있는 경우, catch block의 정보는 프레임 데이터에서 유지가 된다.

  4. PC 레지스터 (PC Registers)

    현재 실행중인 명령문의 주소를 가지는 곳이며, 명령문의 주소를 가지기 위해 각각의 스레드마다 개별 PC 레지스터가 존재한다.

  5. Native Method Stacks

    - Java 외의 Native Code 언어로 작성된 메소드 정보를 가지고 있는 Stack이다.
    - 개별 스레드마다 생성됨.

3. 실행 엔진 (Execution Engine)

실행 데이터 영역에서 할당된 바이트코드는 실행엔진에 의해서 실행된다. 실행엔진은 바이트코드를 읽으며 명령어 단위별로 실행합니다.

  • 인터프리터 (Interpreter)

    - 인터프리터는 바이트코드를 명령어 단위로 빠르게 해석하지만 한줄 씩 수행하기 때문에 실행속도는 느리다.
    - 인터프리터의 단점은 하나의 메소드가 여러번 호출되었을 때, 매번 새로운 해석(interpretation)이 필요하다.

  • JIT 컴파일러 (Just-In-Time)

    JIT컴파일러는 인터프리터의 단점을 보완해준다. 실행엔진은 바이트코드를 변환하는데 인터프리터의 도움을 받지만,
    반복되는 코드가 발견됐을 시에는 JIT컴파일러를 사용해서 반복되는 부분을 native code(원시코드)로 컴파일한다.
    변환된 native code는 인터프리터의 변환 과정없이 직접적으로 사용이 가능하며(기존에는 바이트코드에서 native code로 변환 후 실행하였다면 JIT 컴파일러를 사용하여 변환된 native code는 변환하지않고 바로 실행) 이로 인해 시스템의 성능이 좋아지게 된다. 
JIT 컴파일러 역할
 1. Intermediate Code 생성기 - Intermediate Code를 생성
 2. 코드 최적화 - Intermediate Code를 최적화함.
 3. 타겟 코드 생성 - 원시코드(native code) 생성
 4. 프로파일 - 특정 메소드가 여러번 실행했는지 안했는지를 판별해주는 special componen

 

  • 가비지 컬렉션(Garbage collection)

    - 아무 참조가 없는 불필요한 메모리인 인스턴스들을 모아 정리하는 역할을 한다.
    - System.gc() 메소드로 가비지 컬렉션을 실행할 수 있지만 보장되지는 않는다. (반드시 실행되는건 아니라는 뜻)
    - JVM의 가비지 컬렉션은 생성된 인스턴스들을 모음.

  • etc.

    1. 자바 원시 인터페이스(Java Native Interface, JNI)

        JNI는 Native Method Libraries와 관련있으며, 실행엔진에 필요한 원시 라이브러리들을 제공한다.

    2. 원시 메소드 라이브러리(Native Method Libraries)

        Native Libraries의 집합이며 실행엔진에 필수적이다.

[Reference]


https://dzone.com/articles/jvm-architecture-explained

https://sas-study.tistory.com/262

https://nesoy.github.io/articles/2020-11/ClassLoader

728x90
반응형

댓글