JVM 아키텍쳐

6 minute read

과거 여러번 JVM에 대해 읽어 봤지만
머릿속에 잘 남아있지 않아
이번에 제대로 이해 해보려고 정리한 글입니다.
이 글의 번역이라 봐도 되겠습니다.

VM

: 실제 컴퓨터를 가상화한 기계

  • 한 컴퓨터에 여러 가장 머신이 있을 수 있다.
  • 각 가상머신은 고립되어 있다.
    • 다른 가상머신의 영향을 받지 않는다.(독립적이다.)
  • 실제 컴퓨터 -> Host machine
  • 가상 머신 -> Gueset machine

다른 언어들과 비교

  • C/C++ :
    • 컴파일 언어
    • 해당 플랫폼에 맞는 기계어로 컴파일
  • javascript, python :
    • 인터프리터 언어
    • 컴파일 없이 바로 실행
  • jvm 언어들 :
    • 바이트 코드(.class) 생성(컴파일)
    • 바이트 코드를 JVM이 인터프리트(해석;실행)함
      • 해당 플랫폼에 맞게
      • 컴파일 된 바이트 코드(.class)는
        다른 플랫폼의 JVM에서도 실행가능함
    • 가상 머신과 유사하게
      JVM도 host machine에 독립적

JVM

: Java 프로그램을 실행시킬 수 있는 추상화 머신

  • specification : 규격 ; 상세
    • JVM이 어떻게 작동하는지에 대한 규격
  • implementation : 구현
    • JRE
    • 구현은 Sun 또는 다른 벤더(회사)들이 제공
  • instance : 개체
    • Java class 를 실행하면 JVM 개체가 생성됨

JVM 기능

  • loads the code
  • verify the code
  • executes the code
  • menage memory

JVM architecture

크게 3가지로 이루어짐

  • Class Loader
  • Runtime Memory/Data Area
  • Execution Engine

alt

출처 : https://www.freecodecamp.org/news/content/images/size/w1000/2021/01/image-39.png


Class Loader

  • 프로그램에서
    (.java파일을 컴파일해서 생성된) .class 파일에서 class를 사용할 때
    Class Loaser가 메인 메모리에 해당 class를 로드시킴
  • 보통 main() 메서드를 포함하는 class를
    가장 먼저 메모리에 로드한다.

class loading에는 3가지 상태가 존재

  • Loading
  • Linking
  • Initialization

    alt
    출처 : https://www.freecodecamp.org/news/content/images/size/w1000/2021/01/image-40.png


Loading

: 특정 이름의 class 또는 interface의 바이트 코드를 포함해
원래 class 또는 interface를 생성

  • 3가지 Loader가 있음
    • Bootstrap Class Loader
      • root class Loader
      • superclass Of Extension class loader
        다른 확장 클래스들의 상위 클래스이다.
      • java.lang, java.net, java.util, java.io 등
        기본 패키지들을 로드함
        • 이 패키지들은 rt.jar에 포함됨
        • 다른 주요 라이브러리들은
          $JAVA_HOME/jre/lib 에 포함됨
    • Extension Class Loader
      • Bootstrap Class Loader의 하위 클래스이고
        Application Class Loader의 상위 클래스이다.
      • 확장 라이브러리들을 로드함
        • $JAVA_HOME/jre/lib/ext에 포함된 라이브러리
    • Application Class Loader
      • classpath에 있는 파일들을 로드함
      • classpath의 디폴트는
        현재 애플리케이션의 디렉토리로 설정됨
      • classpath는 command line 옵션으로
        -classpath or -cp로 설정 가능함
  • JVM은 ClassLoader.loadClass()메서드를 이용해
    클래스들을 메모리로 로딩시킨다.

  • 만약 상위 클래스 로더가 클래스를 찾지 못하면
    하위 클래스 로더에게 일을 위임한다.
    마지막까지 클래스를 못찾으면
    NoClassDefFouondError or ClassNotFoundException 을 던진다.

Linking

: class들이 메모리에 로드된 후
linking(연결) 과정을 실행된다.
프로그램에 필요한 클래스 또는 인터페이스
그리고 각각의 의존성들을 포함해 연결한다.(linking)

  • Linking 과정
    1. Verification phase(상;상태):
      • .class 파일이 제약 및 룰에 따라
        구조적으로 알맞는지 확인
      • 만약 맞지 않으면 VerifyException 발생
      • ex) java12에서 작성된 코드가
        Java8이 설정된 시스템에서 실행시키려 하면 실패하게 됨
    2. Preparation phase:
      • JVM은 static field나 기본 초기화 (변수)값들을 메모리에 할당함
    3. Resolution phase:
      • 다른 클래스나 다른 클래수의 상수인 변수(constant variables)를 참조할 때
        이 상태에서 resolve됨
        • resolve는 풀다, 녹이다, 분해하다, 결정하다 이런 뜻인데
          코딩에서는 결정;사용;적용된다 정도로 이해하면 될거 같음

Initialization

: 클래스 로딩의 마지막 과정

  • 클래스나 인터페이스의 메서드를 초기화
  • class’s constructor; 초기화
  • static block을 실행
  • static 변수들의 값 초기화 진행

    ex) private static final boolean enabled = true;

라고 선언 되어 있으면
preparation 상태에서 enabled는 boolean의 기본값인 false로 값이 지정되었다가 Initialization과정에서 true로 값은 지정하게 됨

주의
JVM은 multi-thread 환경에서 실행 될 수 있다.
그래서 multi-thread에서 한 클래스를 동시에 초기화하는
동시성 문제가 있을 수 있으니 이 점을 고려해야 함

Runtime Data Area

아래의 5가지로 구성되어 있음

  • Method Area
  • Heap Area
  • Stack Area
  • Pc Register
  • Native Method Stack

Method Area

  • 모든 클래스 레벨의 데이터들이 저장되어 있음
    • runtime constant pool, field, method data, methods, constructors
  • 만약 메모리가 프로그램 시작에 부족하면
    JVM은 OutOfMemoryError를 던짐
  • VM이 시작할 때 Method Area가 생성됨
  • JVM당 Method Area(1:1)

Heap Area

  • 모든 object와 그와 연관된 변수들이 저장됨
  • 모든 인스턴스, 배열들이 할당되는 메모리 공간
  • vm이 시작할 때 생성됨
  • JVM당 Heap Area(1:1)

주의
multi-thread시 Mathod area 와 Heap area 가 같은 메모리를 공유할 수 있으니 안전하지 않을 수 있음

Stack Area

  • JVM에서 새 쓰레드가 생성될 때 seperate runtime stack 이 동시에 생성됨
  • 지역 변수, 메서드 호출, 및 부분 결과들이 저장됨
    • All local variables, method calls, and partial results are stored in the stack area.
  • 만약 한 쓰레드에서 가능한 스택 크기보다
    더 많은 스택 크기가 요구되면
    StackOverFlowError를 던짐
  • 모든 메서드 호출에 있어서
    Stack Frame이라는 스택 메모리에 한 진입점이 만들어짐
    메서드 호출이 완료되면 Stack Frame이 사라짐

Stack Frame은 아래 세가지로 이루어짐

  • Local variables
    • 각 Frame은 지역 변수들의 배열을 포함하고 그 변수들과 값들은 여기에 저장됨
    • 컴파일 시간에 배열의 길이가 결정됨
  • Operand Stack
    • 각 Frame은 LIFO stack을 포함함
    • 런타임시 workspace로 실행함
      • 런타임시 발생하는 작업때에 메모리 할당의 장소가 됨
    • 컴파일 시간에 최대 스택깊이가 결정됨
  • Frame Data
    • All symbols corresponding to the method are stored here.
      • (method의 심볼? 이름?이 저장되는 장소)
    • 또한 catch block 정보도 포함됨

ex)

double calculateNormalisedScore(List<Answer> answers) {
  
  double score = getScore(answers);
  return normalizeScore(score);
}

double normalizeScore(double score) {
  
  return (score  minScore) / (maxScore  minScore);
}

answer, score 은 Local Variables 배열에 저장됨 Operand Stack 은 계산에 필요한 연산자와 변수들을 포함함
Stack Frame |Local Variables|Operand Stack| |——|—| |List answers|push maxScore| |double score|push minScore| ||push _sub|

  • Stack Area 공유되지 않음. 그래서 스레드에 안전함

Program Counter (PC) Registers

  • JVM은 멀티스레드를 지원함
  • 각 스레드는 JVM을 실행하는 명령 주소를 가진 각자의 PC Register가 있음
  • 한 번 실행하면 PC Register는 다음 명령으로 변경됨

Native Method Stacks

  • Java가 아닌 C/C++ 같은 네이티브 언어를 포함할 수 있는 곳
  • 각 스레드마다 분리된(각자의) navtive method stack이 할당됨

Excution Engine

  • 각 클래스마다 현재 실행해야 할 코드를 실행함
  • 실행되기 전 바이트코드(.class)는 기계어로 변경되야 함
  • JVM은 인터프리터 또는 JIT(just-in-time) 컴파일러를 Execution engine으로 사용

alt

출처 : https://www.freecodecamp.org/news/content/images/2021/01/image-33.png


Interpreter

  • 바이트코드를 한 줄 한 줄 읽고 실행함
  • 한 줄 한 줄 읽으므로 비교적 느림
  • 한 메서드를 여러번 호출할 경우 매번 새로은 해석(interpretation)이 필요함

JIT Compiler

  • interpreter의 단점을 보완
  • 먼저 바이트코드를 실행하기 위해 interpreter를 사용하고
  • 만약 중복되는 코드를 발견하면 JIT 컴파일러를 사용
  • JIT컴파일러가 컴파일 후 native 기계어로 변경
  • navtive 기계어는 중복되는 함수 호출을 바로 사용가능
    • Interpreter처럼 다시 interprete할 필요 없음
  • JIT 컴파일러는 아래 구성요소로 이루어짐
    • Intermediate Code Generator - generates intermediate code
      • 매개(중간) 코드 생성함
    • Code Optimizer - optimizes the intermediate code for better performance
      • 매개 코드 최적화함
    • Target Code Generator - converts intermediate code to native machine code
      • 매개 코드를 네이티브 기계어로 변경함
    • Profiler - finds the hotspots (code that is executed repeatedly)
      • 중복 실행될 코드를 찾음

ex)

int sum = 10;
for(int i = 0 ; i <= 10; i++) {
   sum += i;
}
System.out.println(sum);

인터프리터는 메모리에 있는 sum을 매 루프마다 접근해서 가져와
1을 더하고 다시 메모리에 적용함
즉 매 루프마다 메모리를 접근하는 구조

JIT 컴파일러는 중복되는 코들(HotSpot)를 발견하면
sum의 사본을 스레드의 PC register에 저장해서 매 루프마다 1을 더함
그리고 루프가 끝나면 다시 sum 메모리 접근해서 값을 적용함

Note
JIT compiter는 interpreter가 한 줄 한 줄 해석하는 것보다 시간이 더 걸림
그래서 한 번만 실행하면 되는 코드는 interpreter를 사용하는게 좋음

Garbage Collector

  • Heap area에서 참조되지 않는 객체들을 모아 제거함
  • 사용하지 않는 메모리를 다시 회수하는 작업을 함

두 상태가 있음

  • Mark
    • GC가 메모리에서 사용하지 않는 객체를 확인함
  • Sweep
    • GC가 Mark 단계에서 발견된 객체들을 삭제


  • JVM이 정기적으로 알아서 GC를 함
  • System.gc()해서 호출가능하나 안전하지 않음

JVM은 3가지 다른 타입의 GC를 포함

  • Serial GC
    • 가장 간단한 구현
    • 싱글 스레드의 작은 애플리케이션에서 적합
    • 실행하면 stop the world를 호출
      • 모든 애플리케이션을 정지시킴
    • argument to run: -XX : +UseSerialGC
  • Parallel GC
    • JVM의 default GC구현
    • Throughput Collector라고도 함
    • 멀티 스레드 가능 그러나 stop the world발생하여
      움직이는 애플리케이션을 정지시킴
    • argument to run : -XX:+UseParallelGC
  • G1 GC
    • 큰 heap size를 사용하는 멀티 스레드를 위해 설계된 GC
      • more than 4GB
    • 쓰레기가 가장 많은 곳(메모리 상의)을 확인하고 거기에 먼저 GC 실행
    • argument to run : -XX:+UseG1GC

Concurrent Mark Sweep (CMS) GC is Deprecated since Java 9 and completely removed Java 14

JNI(Java Native Interface)

  • C/C++ 같은 네이티브 코드를 사용할 때 필요
  • 하드웨어와의 통신이 필요하거나
    메모리 관리의 최적화가 필요할 때 필요 (java로 한계가 있을 시)

Native Method Libraries

Native Method Libraries are libraries that are written in other programming languages, such as C, C++, and assembly. These libraries are usually present in the form of .dll or .so files. These native libraries can be loaded through JNI.

일반적인 JVM Errors

  • ClassNotFoundException
    • Class.forName(), ClassLoader.loadClass() or ClassLoader.findSystemClass()을 호출 후
      (클래스를 로드할 때)정의된 클래스가 없으면 발생
  • NoClassDefFouondError
    • 컴파일은 잘 됐지만 런타임시 ClassLoader가 클래스 파일을 찾지 못할 경우 발생
  • OutOfMemoryError
    • 메모리 할당할 공간이 없을 경우
    • GC로 더이상 메모리를 못 생성할 경우(꽉찬상태)
  • StackOverFlowError
    • 스택이 필요한데 더 스택을 사용할 수 없을 경우



참고 : https://www.freecodecamp.org/news/jvm-tutorial-java-virtual-machine-architecture-explained-for-beginners/

Tags: ,

Categories:

Updated:

Leave a comment