JVM, 그래서 넌 뭐야

Jaychy·2021년 4월 21일
3

기술 면접 질문

목록 보기
1/8
post-thumbnail

Hits

[모두가 기술 면접에 합격하는 그날까지]
https://github.com/Lee-Jin-Hyeok/technical-interview-question

1. 개요

JVMJava Virtual Machine의 약자로
자바 바이트코드를 실행할 수 있는 가상 환경이라고 할 수 있다.

JVM 구조를 그림으로 정리하면 위와 같이 정리할 수 있을 것 같다.
처음에는 굉장히 복잡해보이는데 글을 모두 읽고 다시 보면
자연스러운 과정이라는 것을 알 수 있다.


2. Class Loader

클래스 파일(.class 파일, 바이트 코드)을 받으면
이를 토대로 클래스를 로딩하는 일을 담당한다.
Class Loader가 하는 일은 세 가지로 나누어진다.

2-1. Loading

Class Loader가 하는 전반적인 클래스 로딩과 관련된 일들은 모두 여기서 처리한다.
하지만 클래스 로딩을 하는 Class Loader는 크게 세 가지로 나누어진다.

1) Kind of Class Loader

  • Bootstrap Class Loader
    자바에 기본적으로 내장되어 있는 클래스들을 로딩하는 Class Loader이다.
    이러한 내장 클래스들은 rt.jar라는 jar파일에 담겨져있다.
  • Extension Class Loader
    $JAVA_HOME/lib/ext 디렉토리에 존재하는 클래스와
    java.ext.dirs 시스템 변수에 저장된
    클래스들을 로딩하는 Class Loader이다.
  • Application Class Loader
    애플리케이션 수준에 존재하는 클래스들을 로딩하는 Class Loader이다.
    개발자가 직접 작성한 클래스들이 여기에 해당된다.
    System Class Loader라고도 한다.

이외에도 개발자가 직접 Class Loader를 구성하였을 경우
Custom Class Loader를 만들 수 있다.

2) Priority of Class Loader

  • Class Loader Cache
    Class Loader는 처음부터 모든 클래스를 로딩하지 않는다.
    클래스가 필요할 때마다 요청에 의해 클래스를 찾아, 로딩하는데
    이미 로딩된 클래스를 다시 로딩할 필요는 없으므로 클래스를 캐싱해둔다.
  • Bootstrap > Extension > Application(System) > Custom (Bootstrap이 우선순위가 높음)
    Class Loader는 우선순위가 높은 클래스를 로딩한다.
    만약 Bootstrap Class LoaderApplication Class Loader에 같은 클래스가 있다면
    Bootstrap Class Loader에 있는 클래스를 로딩한다는 뜻이다.
    이러한 알고리즘을 위임 계층 알고리즘 (Delegation Hierarchy Algorithm) 이라고 한다.
    하지만 Class LoaderCustom 즉, 우선순위가 낮은 Class Loader부터 탐색하는데
    이는 클래스를 찾으면 바로 로딩하도록 구현된 WAS가 탄생할 수 있도록 했다.
  • Class Not Found Exception
    Bootstrap Class Loader에서도 클래스를 찾지 못하면
    클래스를 찾을 수 없다는 에러를 발생시킨다.

3) Feature of Class Loader

  • Limit Visibility
    하위에 있는 Class Loader는 상위에 있는 Class Loader를 알 수 있지만
    상위에 있는 Class Loader는 하위에 있는 Class Loader를 알 수 없다.
  • Unload Not Possiable
    Class Loader는 로딩될 순 있지만 다시 제거될 순 없다.
  • Name Space
    Class Loader는 클래스를 [패키지명] + [클래스명]으로 Full name을 저장하고
    각각의 Class Loader마다 다른 영역을 가지므로
    같은 이름의 클래스가 로딩되더라도 다른 클래스로 구분지을 수 있다.

2-2. Linking

Linking에서는 바이트코드를 검증하고 준비하는 과정을 거친다.

1) Verify

바이트 코드가 정상적인지 확인한다.

2) Prepare

정적 변수에게 메모리를 할당하고 기본 값을 저장한다.

여기서 기본값이란 기본 타입에게는 0, 참조 타입에게는 null을 의미한다.

3) Resolve

심볼릭 메모리 참조를 실제 메모리 참조로 변경한다.

클래스 파일은 다른 레퍼런스에 대한 참조을 알 수 없기 때문에
심볼릭 메모리 참조로 대체하고 있다.
이제 Class Loader에 의해 메모리에 올라온 클래스를 참조할 수 있으므로
참조하도록 변경하는 것이다.

2-3. Initialization

Initialization 과정에서는 Prepare 과정에서 기본 값을 넣었던 정적 변수에 실제 값을 저장하고,
static initializer를 실행한다.


3. Runtime Data Area

Class Loader가 로딩한 클래스들은 Runtime Data Area의 저장공간에서 관리된다.
Runtime Data Area는 크게 다섯 가지로 나눌 수 있다.

3-1. Method Area

모든 스레드가 공유하는 영역으로, Class Loader에서 로딩한 인터페이스, 클래스의
static variable, static method, static class에 대한 정보를 관리하며,
그 중 Runtime Constant Pool에서는 모든 메소드와 필드의 메모리 참조 주소 테이블을 관리하여
메소드 참조시 Runtime Constant Pool을 통해 매핑한다.

3-2. Heap Area

모든 스레드가 공유하는 영역으로, 클래스의 인스턴스를 저장하는 공간이다.
Heap Area는 크게 세 구역으로 나누어진다.

1) Young Generation

Young Generation은 젊은 객체가 주둔하고 있는 공간이다.
여기서 일어나는 GCMinor GC라고 하고 자원 소모가 적다.

보통 Minor GCStop 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 SurvivorTo Survivor로 구분짓는데
    Minor GC 이후 살아남은 객체들이 Eden 영역에서 From Survivor로 이동한다.
    Survivor 영역이 가득차면 살아있는 객체들은 반대 Survivor 영역으로 이동하게 된다.
    이 과정을 일정 횟수만큼 반복하면 Old Generation으로 이동한다.

    사실 From Survivor에서 To Survivor로 객체가 이동하는 것이 아니라
    From SurvivorTo Survivor의 논리적인 위치를 바꾼다.

2) Old Generation

Old Generation은 늙은 객체가 주둔하고 있는 공간이다.
여기서 일어나는 GCMajor GC라고 하고 자원 소모가 크다.

Major GCFull GC를 혼용하는 경우가 많다.
Major GC는 대상이 Old Generation에 국한되지만
Full GC는 대상이 Heap Area 전체로 넓다.

Old Generation은 보통 Young Generation 보다 넓고 객체의 생명주기가 보통 짧은 특성 때문에
Major GC가 발생하는 빈도는 적다.
하지만 Major GC로 인해 발생하는 Stop The World는 애플리케이션의 성능에 엄청난 영향을 끼칠 수 있다.

3) Metaspace

Metaspace 영역은 Java 8 이후에 생긴 영역으로 기존에 존재하던
Permanent Generation 영역을 대체하기 위해서 등장하였다.

기존 Permanent Generation 영역에서는 JVM에서 어떠한 객체를 설명하는데
필요한 정보들을 저장하는 영역이다.

Permanent GenerationMethod 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 영역을 대체하였다.

MetaspaceJVM이 관리하는 Permanent Generation 영역과는 다르게
OS단에서 관리하여 제한된 크기를 가지는 경우가 없다.

3-3. Stack Area

Stack AreaStack Frame을 저장하는 공간이며 Method Call당 하나씩 생성된다.
스레드 당 하나의 Stack Frame을 가진다.

3-4. PC Register

PC RegisterStack Frame과 마찬가지로 스레드 당 하나의 PC Register를 가지게 된다.
현재 실행 중인 명령의 주소를 가지고 있다.

멀티 스레딩시 PC Register의 값을 토대로 저장했다가 다른 일 하고 다시 돌아올 수 있게 한다.

3-5. Native Method Stack

바이트 코드가 아닌 다른 언어 (C, C++ 등)를 통하여 Stack Frame이 생길 시
Stack Area가 아닌 Native Method StackStack Frame이 쌓이게 된다.
이런 다른 언어를 JVM 위에서 돌아가게 하기 위해서는
JNI (Java Native Interface) 규격에 맞게 적절하게 변경해야 한다.
이렇게 다른 언어로 작성된 코드들을 Native Method Library라고 한다.


4. Execution Engine

Execution Engine은 바이트 코드를 실행하는 주체이다.
Runtime Data Area에 존재하는 바이트 코드를 가져와서 실행하게 된다.

Execution Engine이 바이트 코드를 해석하는 방법은 두 가지가 있다.

4-1. Interpreter

Interpreter 방식은 바이트 코드를 한 줄 한 줄 해석하며 실행한다.
Execution Engine은 일반적으로 이 방식을 채택하지만
한 줄식 해석하는 것이 성능에 좋지 못하고 느리기 때문에
다음과 같은 JIT Compile 방식을 함께 사용한다.

4-2. JIT Compile

JIT Compile 방식은 일정 바이트 코드를 컴파일하여 Native Code로 변경한 뒤
더 이상 Interpreter 방식으로 해석하지 않고 Native Code를 그대로 가져와서
실행하는 방식으로 해석하는 방식이다.

일정 바이트 코드가 여러 번 반복되면 극적인 성능 향상을 볼 수 있지만
한두 번 반복될 코드라면 차라리 Interpreter 방식으로 해석하는 것이 효율적이다.
그래서 Execution Engine은 두 방식을 모두 사용한다.

Garbage Collector의 동작 방식에 대해서는 Heap Area에서 정리해두었으니
다음에는 Garbage Collector의 종류와 그에 따른 동작 방식에 대해 정리할 예정이다.


5. 참조

[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

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

0개의 댓글