Java

JVM(Java Virtual Machine)의 구조 - 메모리

뭉지(moonz) 2022. 3. 2. 15:29

JVM은 클래스 로더 시스템, 메모리, 실행 엔진, 네이티브 메소드 인터페이스(JNI)+네이티브 메소드 라이브러리, 총 4개로 구성되어 있습니다.

 

한번에 다루기 많은 내용이라 나눠서 포스팅할 예정입니다.

  • 메모리  (현재 포스팅)
  • 클래스 로더 시스템과 실행 엔진

 

 

이번 포스팅은 '메모리'에 대해 다루겠습니다.

 

출처 : https://velog.io/@suwon-city-boy/JVM%EC%9D%98-%ED%8A%B9%EC%A7%95-%EA%B5%AC%EC%A1%B0-%EC%8B%A4%ED%96%89%EA%B3%BC%EC%A0%95

 

Runtime Data 영역에는

메서드(Method 혹은 Class) 영역(Area), 스택(Stack) 영역, 힙(Heap) 영역, 네이티브 메서드(Native Method ) 스택 영역으로 구성됩니다. 

 1️⃣ 메서드(클래스) 영역

  • 클래스가 메모리 상에 올라가는 영역입니다.
  • JVM이 동작하고 클래스가 로드될 때 적재되서 프로그램이 종료될 때까지 저장됩니다.
  • 구성
    • 클래스 구조 FQCN(클래스 이름(풀패키지경로), 부모 클래스 이름, 메소드, 변수) 와 인터페이스, 메서드, Enum, 메서드, 변수, 생성자를 위한 코드를 저장합니다.
  • 클래스가 메모리상에서 지워지면 난감하겠죠. 이 영역은 가비지 컬렉션의 영향에서 자유롭고 메모리에 상주하게 됩니다. 
  • 메서드 영역에는 정적 필드 클래스 구조만을 갖고 있습니다. (메서드 정보, 메서드&생성자 코드, 런타임 constant pool도 포함됩니다.)
  • 일반적으로 힙 영역의 논리적 공간이고 Perm Gen의 일부입니다.
참고로, App 클래스의 부모 클래스 App.class.getSuperclass() 는 java.lang.Object입니다.

 

💡 RunTime Constant Pool (in 메서드 영역)

  • class나 interface가 생성될 때 JVM에 의해 생성됩니다. 
  • 각 class/instance마다 별도의 constant pool 테이블이 존재하여 instruction을 수행할 때 참조해야할 정보들을 constant처럼 가지고 있는 영역입니다.
    • constant pool 테이블에는 클래스, 인터페이스 런타임 상수, 정적 메서드나 필드의 실제 메모리 상 주소를 저장해놓고 참조합니다.

 

2️⃣ 스택 영역

자료구조 Stack은 마지막에 들어온 값이 먼저 나가는 LIFO 방식으로 동작합니다. 

아래 그림은 JVM 중 Runtime Data 영역만 보여주는 그림입니다. 

해당 그림에는 Native Method Stack이 빠져있네요! (출처 : https://eminentstar.tistory.com/72)

  • JVM 스레드가 생겨날 때, 해당 스레드를 위한 스택(stack)도 같이 만들어집니다.
    • 즉, 스택 메모리는 스레드별로 생성되고 관리됩니다. (쓰레드를 종료하면 런타임 스택도 사라집니다.)
  • 스택엔 프레임(frame)이 들어갑니다.
  • 스택의 메모리 크기는 '고정된 사이즈' or '필요에 따라 변동 가능한 사이즈', 두 타입이 될 수 있습니다.
    • 고정된 크기의 JVM 스택에서 프로그램 실행 중 메모리 크기가 충분하지 않다면 StackOverFlowError가 발생합니다.
    • 유동적인 크기의 JVM 스택에서 필요에 의해 더 큰 메모리로 확장하지만 할당할 수 있는 추가 메모리가 없을 때 OutOfMemoryError가 발생합니다.

스택 프레임

  • 메소드가 호출될 때마다 프레임이 만들어지며, 메소드 상태 정보를 저장합니다.
  • 현재 실행중인 메서드의 상태를 저장하는 스택 프레임(stack frame)은 JVM 스택에서 top에 위치하게 됩니다.
  • 결국 스택 프레임을 저장하는 용도로 스택이 사용됩니다.
  • 이때 동적 링킹 방식으로 호출된 메서드가 저장되게 됩니다.
  • 그리고, 메서드 호출 범위가 종료되면 스택에서 제거됩니다. (pop이겠지요?)
  • 스택에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴값, 연산시 결과값 등입니다.

 

스택 프레임 내에는 아래 3가지가 존재합니다.

  • Local Variables
  • Operand Stack
  • Constant Pool Reference

💡 Local Variables

정리 예정

 

 

💡 Operand Stack

 

해당 글에서 가져온 코드입니다.
package main;

class Main {
    public int test() {
        int a = 40000000;
        int b = 3;
        String name = Test.name;
        return a + b;
    }
}

class Test {
    public static final String name = "TEST";
}

위의 자바 코드(.java)를 컴파일한 파일(.class)을 역어셈블(javap, 참고)을 통해 바이트코드를 확인할 수 있습니다.

$ javac main.java 
$ javap -c Main.class 
Compiled from "main.java"
class main.Main {
  main.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int test();
    Code:
       0: ldc           #2                  // int 40000000
       2: istore_1
       3: iconst_3
       4: istore_2
       5: ldc           #4                  // String TEST
       7: astore_3
       8: iload_1
       9: iload_2
      10: iadd
      11: ireturn
}

 

💡 Constant Pool Reference


3️⃣  Heap 영역 (객체 영역)

  • 클래스 객체, Class 객체의 인스턴스, 배열 등 객체들이 저장되는 영역입니다.
  • 중요한 점은 가비지 컬렉션이 관리하여 자동으로 메모리가 회수하는 영역이라는 것입니다.  (*GC의 대상)

 

*가비지 컬렉션 (더 이상 참조되지 않는 객체를 모아서 정리)
​은 해당 글에서 정리하였습니다.

 

4️⃣ PC (Program Counter) 레지스터

위에서 보여드린 클래스파일의 바이트 코드들 한줄 한줄을 instruction이라고 부르는데,

PC Register는 현재 Thread에서 실행중인 메서드 내의 실행중인  instruction 주소를 가리키는 포인터를 가지고 있는 역할을 합니다.

다시 위 그림을 가져오면, Stack과 같은 공간(?)에 있는 PC Register

  • 현재 실행중인 메서드(JVM 스택의 top에 있는 Stack Frame) 내에서 현재 실행 중인 instruction(=bytecode)의 주소를 가리키는 포인터가 생성됩니다.
    • PC 레지스터는 어떠한 순간에 수행하고 있는 현재 instruction의 트랙을 keep하는 역할을 합니다.
    • 왜 필요한가? 멀티 스레드 환경에서는 특정 JVM Thread가 RUNNABLE 상태에서 밀려나고, 다시 RUNNABLE 상태가 되었을 때 다시 그 상태부터 작업을 진행하기 때문에 이전까지 수행하던 흐름을 기억해야하기 때문입니다.

만약 현재 수행하는 method가 "natvie"라면 그 PC Register의 값은 undefined일 것입니다.

 

5️⃣ 네이티브 메소드 스택 (Native Method Stack)

JVM은 native 메소드를 지원하기 때문에 native 메소드를 위해 사용되는 스택이 따로 있습니다.

여기에는 Java가 아닌, C, C++로 작성된 메서드의 실행 스택입니다.

  • 만약 native 메소드가 JVM에 의해 로드되지 않으면 스택 또한 필요 없게 됩니다.
  • 메모리 크기 또한 JVM의 스택처럼 고정적이거나 유동적입니다.
  • 네이티브 메소드 스택은 네이티브 메소드 인터페이스(JNI)와 연결되어 있는데, JNI가 사용되면 네이티브 메서드 스택에  저장됩니다.
    참고로, 네이티브 메소드 라이브러리는 JNI를 통해 쓸 수 있습니다. (그래서 이또한 연결되어있습니다.)
    • 왜냐, 네이티브 메소드 라이브러리는 네이티브 메소드 인터페이스의 구현체이기 때문입니다. 

 

더보기

참고) 네이티브 메소드는 자바가 아닌 C, C++로 구현된 메서드를 일컫습니다.

    @HotSpotIntrinsicCandidate
    public static native Thread currentThread();​

 

위 세 영역 (스택, PC 레지스터, 네이티브 메소드 스택)은 특정 스레드에 국한되는 영역입니다. 즉 쓰레드가 생성되면서 위 요소가 생성되어 각각의 쓰레드 단위로 수행됩니다.

더보기

참고

  • 스레드 별 관리되는 경우 : 만약 메모리 영역이 생성된 모든 single 쓰레드마다 유니크하게 할당된다면 이 영역은 스레드가 시작될 때 초기화되고 각각의 스레드가 완료될 때 소멸됩니다.
  • 모든 스레드에 공유되는 경우 : 모든 쓰레드에 메모리 영역이 공유되고 할당된다면, JVM이 시작될 때 초기화되고 종료될 때 소멸됩니다.
    • 힙 영역 > 메서드 영역 > 런타임 컨스턴트 풀
  • https://javapapers.com/core-java/java-jvm-run-time-data-areas/#Program_Counter_PC_Register

 

위 세 영역은 스레드별로 제한되어 공유되는 반면, 아래 힙&메소드 영역은 여러 스레드에서 참조할 수 있는 공유 자원입니다.

두 영역은  VM이 시작될 때 생성되며, 런타임 시 할당된 메모리가 충분하지 않다면 OutOfMemoryError가 발생합니다. 

 

(2) JVM의 구조(java의 메모리 구조)

1) Heap 영역(객체 영역)

객체의 영역이란 객체들이 만들어지고 살다가 죽는 영역이다. 이 영역에서 가장 중요한 존재는 다름 아닌 가비지 컬렉터이다. 

2) Method Area(비객체 영역)

비객체 영역인 Method Area는 클래스가 메모리상에 올라가는 영역이다. 이 영역은 가비지 컬렉터의 영향을 받지 않고 메모리에 상주한다. 간단히 생각해보면 모든 객체는 클래스에서 나오는데 이 클래스가 나도 모르게 메소리상에서 지워지면 난감한 일이 발생할 수 있다. 비객체 영역은 가비지 컬렉터의 영향으로부터 자유롭고, 메모리에 상주하게 되어 있는데 이런 상주의 의미를 ‘static'이라는 뜻으로 사용된다.

(3) static이 붙은 부분은 클래스가 메모리상에 로딩되면서 같이 올라간다.

static 키워드가 붙으면 메소리상에서 다르게 처리된다는 것이다. static이 붙은 변수를 클래스 변수라고 하는 것은 변수가 존재하는 영역이 클래스가 존재하는 영역과 같기 때문이다.

Java에서 모든 객체는 결과적으로는 동일한 클래스에서부터 생성되기 때문에 동일한 클래스에서 나온 모든 객체는 자신이 속한 클래스에 대해서 접근할 수 있다.

static 변수는 이런 방식을 통해서 모든 객체가 동일한 데이터를 사용할 수 있는 공유의 개념을 완성시킨다.

 

 

 

🕯️ 정리

클래스 로더 시스템을 통해 바이트코드를 읽으면서 메모리에 배치를 하고, 

스레드가 실행되면 실행 엔진이 바이트 코드를 한줄 씩 실행하면서 스택에 넣거나 연산을 하는 등 진행하는데 효율을 위해 JIT 컴파일러를 사용합니다.

또한 메모리도 최적화를 위해 참조하고 있지 않은 변수도 정리해주는 역할(GC)도 수행합니다.

메모리나 실행 엔진은 JNI를 통해 네이티브 메소드 라이브러리를 씁니다. (core로 들어갈 수록 꽤 흔히 보입니다.) 

 

 

출처
더 자바, “코드를 조작하는 다양한 방법"
- https://devbox.tistory.com/entry/Java-static
- https://eminentstar.tistory.com/72

https://javapapers.com/core-java/java-jvm-run-time-data-areas/#Program_Counter_PC_Register
피드백은 환영입니다.
수정과 추가가 필요한 부분은 꾸준히 업데이트할 예정입니다.
반응형