I need to know a little JVM

개요

현재 개발자 채용시장에 있어서 Java 가 가장 영향력있는 언어이지 않을까 생각한다. Java 가 영향력이 큰 이유는 아마도 ecosystem 덕분이지 않을까 생각이 든다. 최근에 여러 모던 언어들이 등장하고 있지만, 여전히 Java 에 입지는 굉장히 강력하다. 물론 Kotlin 이라는 언어가 등장하면서 판도의 흐름이 보이고 있다. 둘의 공통점은 JVM 기반 언어라는 점이다. (물론 Kotlin/JS 는 다른이야기 이지만) 이외에도 Scala, Clojure, Groovy 등등 JVM 기반 언어들이 많다. 이번글에서는 JVM 을 high-level 구조를 정리해보려고 한다. 해당 구조는 Java 8 을 기준으로 정리를 했다. 현재 LTS 로 Java 17 까지 출시가 되었으며 변경사항이 있는 것을 참고해야 한다.



JVM 구조

ClassLoader Subsystem

JVM 언어의 철학 중 하나가 Write Once Run Anywhere 이다. compile 시 하드웨어가 바로 이해할 수 있는 low-level 로 번역이 되는 것이 아닌 JVM 이 이해할 수 있는 byte code 로 변환된다. 바로 class 파일 포맷이다.
ClassLoader 의 역할이 바로 compile 된 class 파일들을 runtime 에 동적으로 load 하는 역할을 한다. ClassLoader 로 인해서 runtime system 은 machine 의 file 및 filesystem 을 알 필요가 없다. 모든 class 파일은 한번에 load 되는 것이 아니다. 필요한 시기에 필요한 class 파일을 요청하며 이에따라 요청받은 ClassLoader 가 역할을 한다. 또한 ClassLoader 는 하나가 아니다. 요청한 class path 나 type 에 따라 동적으로 ClassLoader 가 선택된다.

ClassLoader 는 3가지 종류가 있다.

- Bootstrap ClassLoader : java class 가 아닌 c/c++ 로 이루어져있다. JDK internal class 들을 load 는 역할을 하고 있으며 $JAVA_HOME/jre/lib directory 에 위치한 rt.jar 에 있는 class 들을 로드하는 역할을 한다. Bootstrap ClassLoader 는 모든 ClassLoader 의 부모관계이며 Primodial ClassLoader 로 불린다. Java 9 부터는 rt.jar 와 tools.jar 가 lib 폴더에 작은 모듈로 분리되었다.

- Extension ClassLoader : Bootstrap ClassLoader 의 자식관계이다. JDK Extension library 로 부터 class 들을 load 하는 역할을 한다. $JAVA_HOME/jre/lib/ext directory 또는 java.ext.dirs property 로 가리키는 directory 에 있는 class 들을 load 한다. Java 9 버전 부터는 Platform ClassLoader 로 이름이 변경되었다.

- System ClassLoader : Application ClassLoader 로도 불린다. Extension ClassLoader 의 자식관계이다. 환경변수인 CLASSPATH, - classpath 또는 -cp command line option 으로 지정된 class 들을 load 하는 역할을 한다.


ClassLoader 는 3가지 원칙을 따른다.

- Delegation hierarchy principle : class loading 요청을 부모 ClassLoader 에게 forward 한다. 부모 ClassLoader 에서 발견하지 못할 경우 자식 ClassLoader 에서 load 한다.

- Visibility principle : 자식 ClassLoader 는 부모 ClassLoader 에 의해서 load 되는 모든 class 를 볼 수 있다. 반대로 부모 ClassLoader 는 자식 ClassLoader 가 load 하는 class 를 볼 수 없다.

- Uniqueness principle : Delegation hierarchy principle 에 따라서 class 는 한 번만 load 한다. 부모 ClassLoader 가 이미 load 한 class 는 다시 load 하지 않는다. 

모든 ClassLoader 에서 class 를 찾지 못할 경우 NoClassDefFoundError 혹은 ClassNotFoundException 을 던진다.


Runtime data Area Subsystem

Method Area

JVM startup 시간에 생성된다. static 변수를 포함한 class level binary data 가 저장된다. class 상수 풀도 저장된다. 동시에 multiple thread 가 접근이 가능하다. Method area 는 논리적으로는 Heap 영역에 속하지만 Garbage Collection 혹은 Compact 대상에 넣지 않을 수 도 있다. Method Area memory 가 부족할 경우 JVM 은 OutOfMemoryError 를 발생시킨다.

Heap Area

instance 들이 저장된다. multiple thread 가 접근이 가능하여 thread-safe 하지 않다. startup 시 생성 되며 heap size 를 조절할 수 있다. Garbage Collector 에 instance memory 가 관리된다. 설정된 heap size 가 가득 찬 상태에서 추가적인 memory 가 필요할 경우 OutOfMemoryError 가 발생한다. 

Stack Area

thread 마다 하나의 stack 이 별도로 생성된다. thread 에 의해서 행해지는 모든 method call 과 지역 변수들이 stack 에 저장된다. 이를 stack frame 또는 activation record 라고 부른다. stack 을 비우면서 실행되며 stack 이 비었을 때 삭제되며 stack 이 삭제되고 해당 thread 도 종료된다. thread 당 stack 이 할당되고 다른 thread 는 다른 stack 에 접근이 불가능하다. 따라서 thread-safe 하다.

PC registers

thread 생성 시 thread 마다 각각의 Program counter register 가 생성된다. 현재 실행중인 지시 주소를 가지고 있다. 실행 지시가 완료되면 자동으로 PC register 는 다음 지시로 주소를 증가한다.

Native method Stacks

thread 마다 고유의 native method stack 이 생성된다. 해당 thread 에서 요청하는 모든 native method 는 native method stack 에

Execution Engine

class files 을 실행시키는 역할을 한다.

Interpreter

byte code 를 읽고 기계어로 해석하는 역할을 한다. 해석한 기계어를 한 줄 씩 실행한다. 인터프리터의 단점은 같은 method 가 여러번 호출 되어도 매번 해석하기 때문에 성능을 개선할 수 없다.

JIT(Just In Time) Compiler

JIT compiler 의 주된 목적은 성능 향상이다. 내부적으로 JIT Compiler 는 모든 method 에 대해서 count 를 가진다.  JVM 이 method call 이 있을 때 첫째로는 Interpreter 에 의해서 해석된다. 그리고 JIT compiler 가 상응하는 count 변수를 증가시킨다. 해당 프로세스는 모든 method 에 적용된다. method count 변수가 임계값에 도달했을 때 JIT compiler 는 해당 메서드를 (Hot spot) 으로 인식한다. 즉시, JIT compiler 는 해당 method 를 compile 하고 상응하는 native code 를 생성한다. 이후에 JVM 이 해당 method 를 호출하게 되면 JVM 은 Interpreter 를 통해서 실행하지 않고 생성된 native code 를 실행시킨다. 이 과정을 통해서 성능을 향상시킨다. count 변수의 임계값은 JVM 구현체마다 다르다.

Garbage Collector(GC)

Heap 영역의 참조하지 않는 object 를 수집하고 제거하는 역할을 한다. Java9 부터는 G1 GC 가 default 가 되었다.

Java Native Interface(JNI)

c 혹은 c++ 혹은 assembly 작성된 native libraries JVM 에서 사용할 수 있도록 해주는 중재자 역할을 한다.


대표적인 JVM 구현체로는 Hotspot 과 OpenJ9 이 있다.

참조














댓글

이 블로그의 인기 게시물

About JVM Warm up

About idempotent

About Kafka Basic

About ZGC

sneak peek jitpack

Spring Boot Actuator readiness, liveness probes on k8s

About Websocket minimize data size and data transfer cost on cloud

About G1 GC

대학생 코딩 과제 대행 java, python, oracle 네 번째