공부방/JAVA

JVM 내부 구조

EVO. 2023. 7. 15. 18:19

다음 다이어그램은 JVM 사양을 준수하는 Java 가상 머신의 주요 내부 구성 요소를 보여줍니다.

Class Loader 와 JVM에 의해 할당된 메모리 영역인 Runtime Data Areas는 각각 아래에서 설명하겠습니다.


ClassLoader Subsystem

Class Loader 서브시스템은 Java 가상 머신 필수 핵심으로, .class 파일을 loading/reading 하고 바이트 코드를 JVM 메서드 영역에 저장하는 데 사용됩니다. 이 서브시스템은 동적 클래스 Loading을 처리하며 다음과 같은 세 가지 주요 기능을 수행합니다.

 

Loading 

.class 파일을 JVM 메모리로 로드하는 작업을 처리합니다. 메서드 영역에 바이너리 데이터 즉, 정규화된 클래스 이름, 직계 부모,조상 클래스 이름, 메서드, 변수, 생성자 등에 대한 정보 를 저장합니다. 로드된 모든 .class 파일에 대해 JVM은 즉시 java.lang클래스(String,Object 등)을 heap 메모리에 객체를 생성합니다. 개발자가 본인이 만든 여러 개의 클래스를 로드해도 java.lang클래스들은 하나의 클래스 객체만 힙에 생성됩니다. 클래스 로더에는 세가지 주요 유형이 있습니다.

 

  • Bootstrap class loader : 이 클래스 로더는 rt.jar에 있는 내부 핵심 자바 클래스와 java.lang.* 패키지에 있는 기타 클래스를 로드하는 역할을 담당합니다. 다시 말해, JVM을 기동할 때 생성되며, Object 클래스들을 비롯하여 자바 API들을 로드합니다. 다른 클래스 로더와 달리 자바가 아니라 네이티브 코드로 구현되어 있습니다.

 

  • Extension class loader  : 이 클래스 로더는 Bootstrap 클래스 로더의 자식 클래스 이며 확장 클래스 경로(예: jdk\jre\lib\ext)에서 클래스를 로드하는 역할을 담당합니다. 기본 자바 API를 제외한 확장 클래스들을 로드합니다. 다양한 보안 확장 기능 등을 여기에서 로드하게 됩니다.

 

  • Application class loader : 이 클래스 로더는 Extension class loader의 하위 클래스 이며 시스템 클래스 경로에서 클래스를 로드하는 역할을 담당합니다. 내부적으로 'CLASSPATH' 환경 변수를 사용하며 Java 언어로 작성됩니다. 기본 자바 API를 제외한 확장 클래스들을 로드합니다. 다양한 보안 확장 기능 등을 여기에서 로드하게 됩니다.

 

Linking

클래스 또는 인터페이스의 연결을 수행합니다.

 

  • Verification(확인) : 클래스 로더가 .class 파일의 바이트 코드를 자바 언어 명세(Java Language Specification)에 따라서 제대로 잘 작성했는지, JVM 규격에 따라 검증된 컴파일러에서 .class 파일이 생성되는지 등을 확인하여 .class 파일의 정확성을 확인하는 단계입니다. 검증이 실패하면 런타임 에러(java.lang.VerifyError)를 발생시킵니다.

 

  • Preparation(준비) : 클래스 수준 또는 인터페이스 수준 정적 변수에 대한 메모리를 할당하고 기본값을 할당하는 프로세스입니다.  JVM에서 쓰이는 자료구조나 static storage을 위해 메모리를 할당합니다. 또한, 이 단계에서 정적 필드(static field)가 만들어지고 기본값으로 초기화됩니다. 코드에 작성한 원래 값은 Initialization(초기화) 단계에서 할당되므로 아직은 초기화 블록이나 초기화 코드가 실행되지 않습니다.

 

  • Resolution(해석) : method area내의 런타임 상수 풀(run-time constant pool)에 있는 심볼릭 참조를 original 메모리 참조로 변경하는 프로세스입니다.
    심볼릭 참조란 class, field, method의 이름으로 런타임 상수 풀에 존재하며 이 텍스트 이름으로 실제 객체를 검색할 수 있습니다. 이 심볼릭 참조를 실제 메모리 주소 값으로 변경하는 작업입니다.

     

Initialization

이 단계는 코드에 명시된 원래 값이 정적 변수에 할당되고, 정ㅇ적 초기화 블록이 실행되는 클래스 로딩의 마지막 과정입니다. 이 작업은 클래스의 위부터 실행되며, 부모클래스부터 자식클래스까지 한줄 씩 실행됩니다. 클래스 로더를 통한 클래스 탑재 과정이 끝나면 드디어 JVM에서 클래스 파일을 구동시킬 준비가 된 것 입니다. 

 

 

Java Class Loading in JVM

출처 : https://velog.io/@ariul-dev/%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Java-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95

Java에서 클래스는 어떻게 로드가 되나

클래스 로더는 계층적입니다. 애플리케이션의 첫 번째 클래스는 정적 메인 메서드를 사용하여 특별히 로드됩니다. 이후의 모든 클래스는 정적 또는 동적 클래스 로딩 기술을 사용하여 로드됩니다.

 

 

Static class loading : 이 기법에서는 클래스가 new 연산자를 통해 정적으로 로드됩니다.

Dynamic class loading : 이 기법에서는 Class.forName() 또는 loadClass()메서드를 사용하여 클래스를 프로그래밍 방식으로 로드합니다. 

이 둘의 차이점은 전자는 객체를 로드한 후 초기화하는 반면 후자는 클래스만 로드하고 객체는 초기화하지 않는다는 점입니다. 


Runtime Data Areas

JVM Runtime Data Areas

 

  • Method Area : 메서드 영역은 .class 파일의 클래스 수준 데이터인 metadata, static 변수, constant runtime pool , 메서드 코드, 인스턴스 생성을 위한 객체구조,생성자, 필드 등을 저장합니다. 메서드 영역은 JVM 당 하나만 존재하며 모든 클래스 간(모든 스레드들)에 공유 됩니다.그리고 JVM의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면, 실제 물리 메모리 주소로 변환해서 전달해줍니다

 

  • Heap Area : Java로 구성된 객체 및 JRE 클래스들이 저장됩니다. 이곳에는 문자열에 대한 정보를 가진 String Pool 뿐만 아니라, 실제 데이터를 가진 인스턴스, 배열 등이 저장됩니다. Heap 영역 역시 JVM 당 하나만 생성되고스레드 사이에서 공유됩니다. 그리고 Heap 영역이 가득 차게 되면 OutOfMemoryError를 발생시키게 됩니다.

 

  • Stack Area : 스레드가 실행되면 (main()이 있는 메서드도 메인 스레드로 실행) 메서드 호출을 저장하기 위해 별도의 스택이 생성됩니다. 각 스텍 프레임이 스택에 추가됩니다. 스텍 프레임에는 로컬변수배열(메서드 안의 지역변수 들) , 피연산자 스택(메서드 내 연산을 위해서 바이트 코드 명령문들) , 상수 풀에 대한 참조(Referece to Constant Pool : 상수풀을 참조하기위한 공간) 있습니다. 

 

  • PC Register : 자바에서 스레드는 각자의 메서드를 실행합니다. 이때, 스레드 별로 동시에 실행하는 환경이 보장되어야 하므로 현재 실행 중인 JVM에서는 명령어 주소 값(Method Area의 메모리 주소)을 저장할 공간이 필요합니다. 이 부분을 PC Registers 영역이 관리하여 추적하며, 스레드들은 각각 자신만의 PC Registers를 가지고 있습니다.

 

  • Native Method Stacks : 자바로 작성된 프로그램을 실행할 때, 순수하게 자바로 구성된 코드만을 사용할 수 없는 시스템의 자원이나 API가 존재합니다. 다른 프로그래밍 언어로 작성된 메서드들을 Native Method라고 하며, Native Method Stacks는 자바로 작성되지 않은 메서드 정보를 저장하는 영역입니다. 각각의 스레드들이 생성되면 Native Method Stacks도 스레드 별로 생성됩니다. 또한 앞의 JVM Stacks 영역처럼, Native Method가 실행되면 Stack에 해당 메서드가 쌓이게 됩니다.
     

Execution Engine

실행 엔진은 위의 runtime data areas에 할당된 데이터를 읽어 바이트 코드의 명령을 한 줄 씩 실행합니다. 그런데 바이트 코드는 하드웨어가 해석할 수 없기 때문에 약간의 과정이 필요합니다.

 

Interpreter

인터프리터는 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행합니다. 바이트 코드 하나하나의 해석은 빠르지만 실행은 느립니다. 따라서 도입된 JIT 컴파일러가 있습니다.

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

인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후 해당 메서드를 인터프리터 방식으로 읽지 않고 네이티브 코드로 직접 실행하는 방식입니다. 네이티브 코드는 반복되는 바이트 코드를 캐싱하는 방식이기 때문에 한 번 컴파일 된 코드는 더 빨리 실행이 가능합니다. 

하지만 이 컴파일 과정은 인터프리터로 해석하는 것보다 매우 느리기 때문에 JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 실행되는 코드의 반복이 일정 정도를 넘을 때에만 컴파일을 수행합니다.

 

출처 : https://velog.io/@ariul-dev/%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Java-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95#%E2%9D%B7-linking

 


참고

https://velog.io/@ariul-dev/%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Java-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95

 

차근차근 알아보는 Java 프로그램 실행 과정

나는 과연 Java 프로그램 실행 과정을 제대로 이해했을까⁉️ 🤔

velog.io

 

https://www.javacodegeeks.com/2018/04/jvm-architecture-jvm-class-loader-and-runtime-data-areas.html

 

JVM Architecture: JVM Class loader and Runtime Data Areas - Java Code Geeks - 2023

Hello readers, in this tutorial developers will briefly learn about the virtual machine classloader and runtime data areas components.

www.javacodegeeks.com