
[모두가 기술 면접에 합격하는 그날까지]
https://github.com/Lee-Jin-Hyeok/technical-interview-question
JVM은 Java Virtual Machine의 약자로
자바 바이트코드를 실행할 수 있는 가상 환경이라고 할 수 있다.
JVM 구조를 그림으로 정리하면 위와 같이 정리할 수 있을 것 같다.
처음에는 굉장히 복잡해보이는데 글을 모두 읽고 다시 보면
자연스러운 과정이라는 것을 알 수 있다.
클래스 파일(.class 파일, 바이트 코드)을 받으면
이를 토대로 클래스를 로딩하는 일을 담당한다.
Class Loader가 하는 일은 세 가지로 나누어진다.
Class Loader가 하는 전반적인 클래스 로딩과 관련된 일들은 모두 여기서 처리한다.
하지만 클래스 로딩을 하는 Class Loader는 크게 세 가지로 나누어진다.
Class Loader이다.rt.jar라는 jar파일에 담겨져있다.$JAVA_HOME/lib/ext 디렉토리에 존재하는 클래스와java.ext.dirs 시스템 변수에 저장된Class Loader이다.Class Loader이다.System Class Loader라고도 한다.이외에도 개발자가 직접 Class Loader를 구성하였을 경우
Custom Class Loader를 만들 수 있다.
Class Loader CacheClass Loader는 처음부터 모든 클래스를 로딩하지 않는다.Bootstrap > Extension > Application(System) > Custom (Bootstrap이 우선순위가 높음)Class Loader는 우선순위가 높은 클래스를 로딩한다.Bootstrap Class Loader와 Application Class Loader에 같은 클래스가 있다면Bootstrap Class Loader에 있는 클래스를 로딩한다는 뜻이다.위임 계층 알고리즘 (Delegation Hierarchy Algorithm) 이라고 한다.Class Loader는 Custom 즉, 우선순위가 낮은 Class Loader부터 탐색하는데WAS가 탄생할 수 있도록 했다.Class Not Found ExceptionBootstrap Class Loader에서도 클래스를 찾지 못하면Limit VisibilityClass Loader는 상위에 있는 Class Loader를 알 수 있지만Class Loader는 하위에 있는 Class Loader를 알 수 없다.Unload Not PossiableClass Loader는 로딩될 순 있지만 다시 제거될 순 없다.Name SpaceClass Loader는 클래스를 [패키지명] + [클래스명]으로 Full name을 저장하고Class Loader마다 다른 영역을 가지므로Linking에서는 바이트코드를 검증하고 준비하는 과정을 거친다.
바이트 코드가 정상적인지 확인한다.
정적 변수에게 메모리를 할당하고 기본 값을 저장한다.
여기서 기본값이란 기본 타입에게는 0, 참조 타입에게는 null을 의미한다.
심볼릭 메모리 참조를 실제 메모리 참조로 변경한다.
클래스 파일은 다른 레퍼런스에 대한 참조을 알 수 없기 때문에
심볼릭 메모리 참조로 대체하고 있다.
이제Class Loader에 의해 메모리에 올라온 클래스를 참조할 수 있으므로
참조하도록 변경하는 것이다.
Initialization 과정에서는 Prepare 과정에서 기본 값을 넣었던 정적 변수에 실제 값을 저장하고,
static initializer를 실행한다.
Class Loader가 로딩한 클래스들은 Runtime Data Area의 저장공간에서 관리된다.
Runtime Data Area는 크게 다섯 가지로 나눌 수 있다.
모든 스레드가 공유하는 영역으로, Class Loader에서 로딩한 인터페이스, 클래스의
static variable, static method, static class에 대한 정보를 관리하며,
그 중 Runtime Constant Pool에서는 모든 메소드와 필드의 메모리 참조 주소 테이블을 관리하여
메소드 참조시 Runtime Constant Pool을 통해 매핑한다.
모든 스레드가 공유하는 영역으로, 클래스의 인스턴스를 저장하는 공간이다.
Heap Area는 크게 세 구역으로 나누어진다.
Young Generation은 젊은 객체가 주둔하고 있는 공간이다.
여기서 일어나는 GC는 Minor GC라고 하고 자원 소모가 적다.
보통
Minor GC는Stop the World현상이 일어나지 않는다고 생각하는 경우가 많은데
이는 사실이 아니고 살아있는 객체로 인해Eden영역이 가득차서Survivor영역으로 옮기는 경우
Stop the World현상이 발생하여 생각보다 많은 시간 동안 모든 스레드가 멈춘다.
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc
Young Generation 영역은 Eden 영역 하나와 Survivor 영역 두 개로 나누어진다.
Eden 영역Eden 영역에 저장된다.Eden 영역이 가득차게 되면 Minor GC가 발생한다.Survivor 영역Survivor 영역은 From Survivor과 To Survivor로 구분짓는데Minor GC 이후 살아남은 객체들이 Eden 영역에서 From Survivor로 이동한다.Survivor 영역이 가득차면 살아있는 객체들은 반대 Survivor 영역으로 이동하게 된다.Old Generation으로 이동한다.사실
From Survivor에서To Survivor로 객체가 이동하는 것이 아니라
From Survivor와To Survivor의 논리적인 위치를 바꾼다.
Old Generation은 늙은 객체가 주둔하고 있는 공간이다.
여기서 일어나는 GC는 Major GC라고 하고 자원 소모가 크다.
Major GC와Full GC를 혼용하는 경우가 많다.
Major GC는 대상이Old Generation에 국한되지만
Full GC는 대상이Heap Area전체로 넓다.
Old Generation은 보통 Young Generation 보다 넓고 객체의 생명주기가 보통 짧은 특성 때문에
Major GC가 발생하는 빈도는 적다.
하지만 Major GC로 인해 발생하는 Stop The World는 애플리케이션의 성능에 엄청난 영향을 끼칠 수 있다.
Metaspace 영역은 Java 8 이후에 생긴 영역으로 기존에 존재하던
Permanent Generation 영역을 대체하기 위해서 등장하였다.
기존 Permanent Generation 영역에서는 JVM에서 어떠한 객체를 설명하는데
필요한 정보들을 저장하는 영역이다.
Permanent Generation과Method Area는 본질적으로 가지고 있는 정보가 다른 것 같다.
(Static한 정보들을 가지고 있다고 알고 있었는데 정확하게는 모르겠다.)A third generation closely related to the tenured generation is the permanent generation. The permanent generation is special because it holds data needed by the virtual machine to describe objects that do not have an equivalence at the Java language level. For example objects describing classes and methods are stored in the permanent generation.
<참조 https://stackoverflow.com/questions/9095748/method-area-and-permgen>
이런 Permanent Generation 영역은 제한된 크기를 가지고 있어
OOME (Out of Memory Error)를 발생시켜 개발자들의 골칫거리를 만들었다.
그래서 Java 8 이후 부터는 Metaspace라는 영역이 Permanent Generation 영역을 대체하였다.
Metaspace는 JVM이 관리하는 Permanent Generation 영역과는 다르게
OS단에서 관리하여 제한된 크기를 가지는 경우가 없다.
Stack Area는 Stack Frame을 저장하는 공간이며 Method Call당 하나씩 생성된다.
스레드 당 하나의 Stack Frame을 가진다.
PC Register도 Stack Frame과 마찬가지로 스레드 당 하나의 PC Register를 가지게 된다.
현재 실행 중인 명령의 주소를 가지고 있다.
멀티 스레딩시 PC Register의 값을 토대로 저장했다가 다른 일 하고 다시 돌아올 수 있게 한다.
바이트 코드가 아닌 다른 언어 (C, C++ 등)를 통하여 Stack Frame이 생길 시
Stack Area가 아닌 Native Method Stack에 Stack Frame이 쌓이게 된다.
이런 다른 언어를 JVM 위에서 돌아가게 하기 위해서는
JNI (Java Native Interface) 규격에 맞게 적절하게 변경해야 한다.
이렇게 다른 언어로 작성된 코드들을 Native Method Library라고 한다.
Execution Engine은 바이트 코드를 실행하는 주체이다.
Runtime Data Area에 존재하는 바이트 코드를 가져와서 실행하게 된다.
Execution Engine이 바이트 코드를 해석하는 방법은 두 가지가 있다.
Interpreter 방식은 바이트 코드를 한 줄 한 줄 해석하며 실행한다.
Execution Engine은 일반적으로 이 방식을 채택하지만
한 줄식 해석하는 것이 성능에 좋지 못하고 느리기 때문에
다음과 같은 JIT Compile 방식을 함께 사용한다.
JIT Compile 방식은 일정 바이트 코드를 컴파일하여 Native Code로 변경한 뒤
더 이상 Interpreter 방식으로 해석하지 않고 Native Code를 그대로 가져와서
실행하는 방식으로 해석하는 방식이다.
일정 바이트 코드가 여러 번 반복되면 극적인 성능 향상을 볼 수 있지만
한두 번 반복될 코드라면 차라리 Interpreter 방식으로 해석하는 것이 효율적이다.
그래서 Execution Engine은 두 방식을 모두 사용한다.
Garbage Collector의 동작 방식에 대해서는Heap Area에서 정리해두었으니
다음에는Garbage Collector의 종류와 그에 따른 동작 방식에 대해 정리할 예정이다.
[JVM Structure]
https://dzone.com/articles/jvm-architecture-explained[Minor GC vs Major GC vs Full GC]
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc