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 하지 않는다.
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 이 있다.
댓글
댓글 쓰기