뭐라도 끄적이는 BLOG

JVM 구성 요소 본문

Java/Java 기본

JVM 구성 요소

Drawhale 2023. 6. 29. 06:07

JVM 구성요소

사진 출처 : dzone.com/articles/jvm-architecture-explained

  • Class Loader
  • Runtime Data Area
  • Execution Engine
  • Garbage Collector

① Class Loader

Class Loader는 실행할 각각의 클래스 파일들을 찾아서 JVM의 메모리에 탑재해주는 역할을 한다. 이뿐만아니라 JVM에 관련된 Loading, Linking, Initialization 3가지 역할또한 맡게 된다.

  • Loading: 클래스 파일을 탑재하는 과정
  • Linking: 클래스 파일을 사용하기 위해 검증하고, 기본 값으로 초기화하는 과정
  • Initialization: static field의 값들을 정의한 값으로 초기화를 하는 과정

Loading

ClassLoader가 필요한 클래스 파일들을 찾아 탑재하게 된다. 각각의 클래스 파일들이 기본으로 제공받는 클래스 파일인지 혹은 개발자가 정의한 클래스 파일인지와 같은 기준에 따라서 ClassLoader의 수준은 BootStrap ClassLoader, Extension ClassLoader, Application ClassLoader 세가지로 나뉘게 된다.

BootStrap ClassLoader

BootStrap ClassLoader는 다른 모든 ClassLoader의 부모가 되는 ClassLoader이다. rt.jar를 포함하여, JVM을 구동시키기 위한 가장 필수적인 라이브러리 클래스들을 JVM에 탑재하게 된다. 가장 상위의 ClassLoader이므로 다른 ClassLoader과는 다르게 탑재되는 운영체제에 맞게 네이티브 코드로 작성되어 있다.

java9에서부터 rt.jar, tools.jar등 기본으로 제공되던 jar 파일이 없어지고 안에 있던 내용들은 모듈 시스템에 맞게 더 효율적으로 재편되어 lib폴더 안에 저장된다.

java -verbose:class Main

초기 Load되는 class

Extension ClassLoader(java 8), Platform ClassLoader(java 9)

jre/lib/ext 폴더나 java.ext.dirs환경변수로 지정된 폴더에 있는 클래스 파일을 로딩한다. Java로 구현되어 있어 sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있으며, URLClassLoader를 상속하고 있다.

java9(Platform ClassLoader)에서 부터는 jre/lib/ext, java.ext.dirs를 지원하지 않고 JavaSE의 모든 클래스와 Java SE에는 없지만 JCP에 의해 표준화 된 모듈 내의 클래스를 볼 수 있으며, Java8에 비해 볼 수 있는 범위가 확장되었다. URLClassLoader가 아닌 BuiltinClassLoader를 상속받아 ClassLoaders클래스 내부 static 클래스로 구현되어 있다.

Application ClassLoader(java 8), System ClassLoader(java 9)

Application ClassLoader는 -classpath(-cp)나 JAR 파일 안에 있는 Manifest파일의 Class-Path 속성 값으로 지정된 폴더에 있는 클래스를 로딩한다. Java로 구현되어 있으며, sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있고 URLClassLoader를 상속하고 있다. 개발자가 애플리케이션 구동을 위해 직접 작성한 대부분의 클래스는 Application ClassLoader에 의해 로딩된다.

java9(System ClassLoader)에서는 classpath, modulepath에 있는 클래스를 로딩하며 URLClassLoader가 아닌 BuiltinClassLoader를 상속받아 ClassLoaders클래스의 내부 static 클래스로 구현되어있다.

위 3가지 ClassLoader들을 모두 거쳤는데도, 클래스 파일을 찾지 못하게 되면 ClassNotFoundException 예외를 던지게 된다.

클래스 로더 동작 3원칙

  1. 위임 원칙(Delegation Principle): 클래스 로딩 작업을 사위 클래스로더에 위임한다.
  2. 가시범위 원칙(Visibility Principle): 하위 클래스로더는 상위 클래스로더가 로딩한 클래스를 볼 수 있지만, 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없다.
  3. 유일성 원칙(Uniqueness Principle): 하위 클래스로더는 상위 클래스로더가 로딩한 클래스를 다시 로딩하지 않게 해서 로딩된 클래스의 유일성을 보장한다.

Linking

Linking은 로드된 클래스 파일들을 검증하고, 사용할 수 있게 준비하는 과정을 의미한다. Linking은 Verification, Preparation, Resolution 3가지 단계로 이루어져 있다.

Verification

클래스 파일이 유효한지를 확인하는 과정이다. 클래스 파일이 JVM의 구동 조건대로 구현되지 않았을 겨우 VerifyError를 던지게 된다.

Preparation

클래스 및 인터페이스에 필요한 static field 메모리를 할당하고, 이를 기본값으로 초기화를 한다. 기본값으로 초기화된 static field값들은 뒤의 Initialization과정에서 코드에 작성한 초기값으로 변경이 된다. 이 때문에 JVM에 탑재된 클래스 파일의 코드를 작동시키지는 않는다.

Resolution

Symbolic Reference 값을 JVM의 메모리 구성 요소인 Method Area의 런타임 환경 풀을 통하여 Direct Reference라는 메모리 주소 값으로 바꾸어준다. 해당 단계의 영향을 받는 JVM Instruction 요소는 new및 instanceof가 있다.

Initialization

Initialization단계에서는 클래스 파일의 코드를 읽게된다. Java 코드에서의 class와 interface의 값들을 지정한 값들로 초기화 및 초기화 메서드를 실행시켜준다. 이 때, JVM은 멀티 쓰레딩으로 작동을 하며, 같은 시간에 한 번에 초기화를 하는 경우가 있기 때문에 초기화 단계에서도 동시성을 고려해주어야 한다. ClassLoader를 통한 클래스 탑재 과정이 끝나면 본격적으로 JVM에서 클래스 파일을 구동시킬 준비가 끝나게 된다.

② Runtime Data Area

Class Loader로부터 분석된 데이터를 저장하고 실행 도중 필요한 데이터를 저장하는 영역을 Runtime Data Area라고 한다. 해당 영역은 메모리에 올라간 클래스, 객체, 변수들이 저장되는 곳으로 크게 Method Area, Heap Area, Stack Area, PC registers, Native Method Stack 다섯 개 영역으로 구분이 되어 있다.

클래스 파일이 실행되면 Method Area에서 클래스 정보를 복사하여 Heap Area에 실행할 클래스를 위한 메모리를 할당한다. 또한 클래스를 실행하면 데이터가 저장되는 영역으로 프로그램 실행에 영향을 미칠 수도 있다. 그리고 JVM성능 향상을 위한 튜닝이 가능한 영역이기도 하다.

ⓐ Method Area

Method Area는 ClassLoader에 의해서 로딩된 클래스가 저장되는 곳이다. Runtime Constant Pool과 static변수, 메소드와 클래스 변수로 구성되어 있는데 이를 클래스의 메타 정보라고 한다. JVM당 하나만 생성이되며 인스턴스 생성에 필요한 정보도 존재하기 때문에 JVM의 모든 Thread들이 Method Area를 공유하게 된다. JVM에서 클래스를 실행하면 메소드 영역에서 클래스 정보를 복사하고 힙 영역에서 메모리를 할당하여 실행한다. 기초 역할을 하므로 JVM 구동시작 시에 생성이 되며, 종료 시 까지 유지된다. GC의 관리 대상이기도 하다.

ⓑ Heap Area

Heap Area는 JVM의 실행 데이터 영역 중에서 가장 중요한 역할을 담당한다. 객체는 클래스가 실행될 때 생성돼서 Heap Area에 저장된다. 그러므로 Heap Area는 JVM에서 가장 중요한 데이터를 저장함과 동시에 매우 세밀한 관리가 이루어지는 곳이다. Heap Area는 다시 여러 개의 영역으로 나눠져 있다.

사진 출처: medium.com/platform-engineer/understanding-java-memory-model-1d0863f6d973

Heap Area는 크게 Young, Old로 나뉘어 있다.

1. Young Generation

Young의 각 영역들은 얼마나 데이터를 오랫동안 저장하고 있어야 하는지에 따라서 구분된다. Young영역은 프로그램 내부에 어떤 코드가 실행되면서 새롭게 생긴 데이터가 저장되는 부분이고, Young영역의 데이터가 계속돼서 사용되면 Old영역으로 이동하게 된다.

Young 영역에 저장된 데이터의 사용이 끝나고 일정 시간 동안 사용되지 않으면 그 데이터는 지워진다. 이때 데이터를 지우는 것은 GC가 담당한다. GC는 아래 Minor GC에서 자세히 이야기 한다.

2. Old Generation

빈번하게 사용되는 데이터는 Young에서 Old영역으로 이동된다. Young 영역과 비교했을 때 Old 영역에서는 오랫동안 데이터가 보관된다.

Heap Area를 2가지 영역으로 나눠 저장하는 이유는 보다 효율적으로 데이터를 관리하기 위함이며 가비지 컬렉터가 실행되는 동안 발생하는 STW(Stop The World)를 줄이기 위해서이다.

사진 출처: medium.com/platform-engineer/understanding-java-memory-model-1d0863f6d973

Permanent Generation → Metaspace

Permanent Generation은 클래스에 대한 정보가 저장되는 곳이다. JVM에서 클래스 정보를 바탕으로 메모리 위에 객체를 빠르게 생성하기 위해 저장되는 곳이다. JDK8 이후 permGen은 Metaspace 영역으로 변경되었다. 이제 더 이상 Heap이 아닌 Native Memory이다. (www.javainuse.com/java/metaspace)

https://johngrib.github.io/wiki/java8-why-permgen-removed/

 

JDK 8에서 Perm 영역은 왜 삭제됐을까

 

johngrib.github.io

ⓒ Stack Area

클래스 실행은 메소드 호출을 의미한다. 그리고 메소드는 다른 메소드를 호출 할 수 있다. 이렇게 호출된 메소드 정보들이 쌓이는(저장되는) 곳이 바로 Stack Area이다. 각 스레드를 위해 분리되어 있으며 메소드를 호출할 때 마다 Stack Frame으로 불리는 Entry가 Stack Area에 생성된다. 해당 영역은 실행이 끝나면 저장된 정보는 삭제된다.

 

 

 

 

ⓓ PC Register Area

PC Register Area는 현재 JVM이 수행할 명령어의 주소를 저장하는 공간이며 이 영역은 개발자가 특별히 개입할 필요가 없는 영역이다. 각 Thread마다 하나씩 존재한다.

ⓔ Native Method Stacks

Native Method란 OS의 시스템 정보, 리소스를 사용하거나 접근하기 위한 코드로 C, C++작성되어 있다. Native Method Stakcs는 이러한 매개변수나 지역 변수 등 Native Method를 위한 영역으로 이에 대한 정보가 저장된다.

자바 프로그램은 OS사이에 JVM이 존재하고 있어 시스템에 직접 접근하기 어려운 형태이다. 하지만 JNI(Java Native Interface) API를 사용하면 자바 프로그램이 OS 시스템에 대한 접근이 가능하다. 이때 사용되는 네이티브 메소드들에 대한 정보가 Native Method Stacks에 저장된다.

③ Execution Engine

Class Loder에 의해 Load 된 Class의 ByteCode를 실행하는 Runtime Module이 Execution Engine이다. Class Loader작업이 끝난 Class 파일은 Runtime Data Area의 Method Area에 배치된다. JVM은 Method Area의 Byte Code를 Execution Engine에 제공하여 Class에 정의된 내용대로 실행하게 된다.

Interpreter

바이트 코드를 해석하여 실행하는 역할을 수행한다. 같은 메소드라도 여러번 호출될 때 매번 새로 수행되어야 하지만 JIT로 이러한 문제점을 해소하였다.

JIT 컴파일러란 무엇이며 어떻게 동작하는가

JIT(Just In Time) 컴파일러는 프로그램이 실제 실행되는 시점(런타임)에서 기계어로 번역하는 컴파일 기법이다. 전통적으로 컴퓨터 프로그램을 만드는 방법은 크게 인터프리트 방식과 컴파일 방식으로 나눌 수 있다. JIT 컴파일러는 이 두 가지의 방식을 혼합한 방식으로 실행 시점에서 인터프리트 방식으로 기계어 코드를 생성하면서 필요할 경우 코드를 캐싱하여 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다. JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고 컴파일을 수행하여 Native Code로 변경 및 캐싱한다.

④ Garbage Collector

메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리를 필요가 없어지면 해제하는 기법을 말한다. 보통 어떤 변수도 가리키지 않게 된 영역을 해제한다. GC의 동작 시간은 일정하게 정해져 있지 않기 때문에 언제 객체를 정리할지는 알 수 없다. 또한 GC를 수행하는 동안 GC Thread를 제외한 다른 모든 Thread는 일시 정지 상태가 된다.

Grabage Collector의 구현은 각 Vendor마다 다르다. 하지만 대체로 아래와 Mark and Sweep 과정을 따르게 된다.

  1. Garbage collector가 Stack의 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
  2. Reachable Object가 참조하고 있는 객체도 찾아서 마킹한다. (1, 2 Mark)
  3. 마킹되지 않은 객체를 Heap에서 제거한다. (Sweep)

ⓐ Minor GC

Young Generation에서 발생하는 GC를 MinorGC라고 한다. Heap영역의 Young Gen에서 Eden의 사용량이 가득찻을때 발생하는 GC이다. Eden영역에서 Mark and Sweep과정이 발생한다. 이 과정에서 살아남은 객체는 Survival0로 옮겨지게 된다.

Survival0의 사용량이 가득 차면 또 GC가 이루어지며 살아남은 객체는 Survival1로 옮겨지게 된다. 이때 이동한 객체는 Age값이 증가 한다. 이때 Survival0는 비워두고 Survival1에서 Eden영역에서 살아남은 객체를 저장하게 된다. 이렇게 Survival0와 Survival1을 번갈아가면서 사용한다고 생각하면 된다.

Survival에서 반복적으로 살아남은 객체는 Age값이 특정값 이상이 되면 Old Generation으로 이동하게 되는데 이과정을 Promotion이라고 한다.

ⓑ Major GC

Old Generation (Tenured Space)에서 발생하는 GC를 MajorGC라고 한다. MinorGC에서 Promotion으로 이동한 객체들이 Old Generation영역을 모두 차지하게 되면 발생하는 GC이다.

Major GC를 수행하면서 Young Generation과 Old Generation 모두 수집하게 되어 Full GC와 동의어라고 할수도 있으나  MajorGC는 Old Generation에서 수집에 특화된 용어로 사용하고 Full GC는 조금더 넓은 의미를 가지고 있을 수도 있다.

ⓒ Full GC

Young Generation과 Old Generation을 모두 수집하는 가비지 컬렉션이다. Major GC와 차이를 두고 Heap 메모리 이외 다른 리소스의 정리 작업을 포함하는 경우도 있다. 

Oracle에선 Minor GC와 Major GC에 대한 설명 밖에 정의 되어 있지 않으나 G1 GC튜닝 과정에서 Full GC라는 용어가 등장한다.

Garbage Collector의 종류는 다음 포스트에서 소개한다.

참고 자료

 

Java 클래스로더 훑어보기

Java ClassLoader 훑어보기아주 예전에 SCJP 시험볼 때나 살펴본 이후로 자바의 클래스로더를 직접 다뤄야 할 일은 솔직히 없었다. 그래서 거의 잊고 살아왔는데 요즘 Quartz를 다루면서 Quartz에 없는 기

homoefficio.github.io

 

JVM에 관하여 - Part 2, ClassLoader

Java 로 작성한 코드는 어떻게 돌아가는 걸까? 해당 물음에 답을 찾기 위한 JVM 시리즈 2편, JVM 의 구성 요소 중 ClassLoader 에 관한 글입니다. 이번 글에서는 ClassLoader…

tecoble.techcourse.co.kr

 

반응형

'Java > Java 기본' 카테고리의 다른 글

Java 연산자  (0) 2023.07.01
Java Variables와 Data Types  (0) 2023.06.30
Garbage Collector의 종류 (Serial, Parallel, CMS, G1, Z)  (0) 2023.06.29
Java Bytecode  (0) 2023.06.29
Java (JVM, JIT, JDK 간략한 설명)  (0) 2023.06.29