본문 바로가기
자바

JVM과 Call Stack (Stack, Frame)

by 나무후추통 2023. 1. 29.

그림 1 Java SE 7 JVM 구조 일부 (출처 : JVM Internals)

Stack?

그림 2 LIFO 스택 (출처 : Wikipedia)

  • 스택은 자료를 넣고(Push), 가장 최근에 넣은 자료를 뺄 수 있는(Pop) 동작을 가진 Abstract Data Type(ADT)이다.
  • 이처럼 먼저 넣은 자료가 나중에 나오는 것을 LIFO(Last In, First Out) 구조라고 한다.

Call Stack?

  • 컴퓨터의 서브루틴(== 프로시저 == 함수 == 메소드) 정보를 저장하기 위해 사용하는 스택 자료구조이다.
  • Control Stack, Runtime Stack, Execution Stack, Stack 라고도 불린다.

그림 3 Call Stack, 1에서 2 호출
그림 4 Call Stack, 2에서 3 호출
그림 5 Call Stack, 3에서 2로 리턴
그림 6 Call Stack, 2에서 1로 리턴

  • P1 -> P2를 호출할 때 넘기는 파라미터와 P1 리턴주소를 Call Stack에 Push한다. (P2->P3도 동일)
  • P2 -> P1로 리턴할 때 P2는 자기 자신의 local variable만 Pop을 하고 파리미터는 다음 제어권을 가진 P1이 Pop한다.
    • 이 구조는 CPU, 기계어 아키텍처마다 다르다. 제어권을 넘기기 전에 파라미터를 Pop할 수도 있다. 

JVM에서 Stack, Frame

1. Stack

그림 7 JVM Stack

  • JVM은 위 Call Stack과 같은 구조를 스레드 단위로 나누어 관리한다.
  • 스레드가 생성되면 Stack도 같이 만들어진다.
    • 스레드가 허용 Stack size를 넘기면 StackOverflowError 발생
    • Stack size는 동적으로 할당이 가능하다. 메모리가 부족하다면 OutOfMemoryError 발생
  • 여기서 Frame은 위 Call Stack에서 local variable, return address, parameters를 포함하는 하나의 단위이다.
    • Frame은 위 Call Stack에서 설명한 요소를 가지는 것이 아니다. (이는 아래에 설명한다.)
    • Call Stack에 쌓인 요소들이 프로시저에 마다 색깔(파랑, 초록, 빨강)로 구별되는데, 이 하나의 단위를 Frame으로 이해하면 된다.

2. Frame

  • 새로운 프레임은 메소드가 호출되었을 때 생성되고 메소드가 종료될 때 사라진다.
  • 프레임은 Local variables, Operand stack, Reference to runtime constant pool 로 구성되어 있다.

2.1 Local Variables

  • Zero-based array of words로 구성되어 있다.
    • zero-based array : 0부터 시작하는 배열
    • word : 컴퓨터의 기본처리 단위 (32비트 컴퓨터 : 기본 처리 단위가 32비트이다.)
  • double, long을 제외한 나머지 primitve type, references, return address 모두 1칸의 공간을 차지한다.
    • double, long은 64bits 크기를 가지므로 2개의 공간을 차지한다.
  • byte, short, char, boolean 타입은 local variable 안에서 int로 변환이 된다. (이는 operand stack에서도 동일하게 일어난다.)
  • 호출된 메소드가 static, non-static에 따라 frame 구성이 달라진다.
// 출처 : https://www.artima.com/insidejvm/ed2/jvm8.html
class Example3a {

    public static int runClassMethod(int i, long l, float f,
        double d, Object o, byte b) {

        return 0;
    }

    public int runInstanceMethod(char c, double d, short s,
        boolean b) {

        return 0;
    }
}

그림 8 local vairable (출처 : Inside JVM 2ed)

  • runClassMethod는 메소드 영역에 저장되어 있다.
    • 객체 생성없이 클래스 이름으로 호출이 가능하다.
    • runInstanceMethod와 달리 0 인덱스에 'this'가 없다.
  • runInstanceMethod는 객체를 생성해야 존재한다
    • 해당 instance data에 접근이 가능해야 하므로 'this'가 0에 위치한다.

2.2 Operand Stack

  • array of words로 구성되어 있다.
    • Operand(피연산자)는 연산의 대상을 나타내는 데이터 혹은 연산에 사용할 데이터를 지정(메모리 공간)을 뜻한다.
    • 단, Stack이란 이름에서 알 수 있듯이 스택 구조로 동작한다. (local vairable와 달리 인덱스로 접근할 수 없다.)
  • 값을 저장하는 작업 공간으로 활용된다.
// Main.java
class Main {
    
    void add() {
        int a = 100;
        int b = 98;
        int c = a+b;
    }
}
// Main.class
class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void add();
    Code:
       0: bipush        100
       2: istore_1
       3: bipush        98
       5: istore_2
       6: iload_1
       7: iload_2
       8: iadd
       9: istore_3
      10: return
}
       0: bipush        100 byte(100)을 int로 변환 후 stack에 push
       2: istore_1 stack에서 pop한 뒤, local variable 인덱스 1에 저장
       3: bipush        98 byte(98)을 int로 변환 후 stack에 push
       5: istore_2 stack에서 pop한 뒤, local variable 인덱스 2에 저장
       6: iload_1 local variable 인덱스 1의 값을 stack에 push
       7: iload_2 local variable 인덱스 2의 값을 stack에 push
       8: iadd stack에서 두 번 pop하고 add, 다시 push
       9: istore_3 stack에서 pop한 뒤, local variable 인덱스 3에 저장
      10: return void 리턴

그림 9 Bytecode를 그림으로 표현

2.3 Dynamic Linking (References to Runtime Constant Pool)

  • 각 프레임은 run-time constant pool의 참조를 포함한다.
// Main.java
class Main {
    
    public void test() {
        Test t = new Test();
        String name = "hello";
        int big = 100000000;
        t.add();
    }
}

class Test {

    public int add() {
        int a = 1;
        int b = 2;
        return a+b;
    }
}
// Main.class
class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void test();
    Code:
       0: new           #2                  // class Test
       3: dup
       4: invokespecial #3                  // Method Test."<init>":()V
       7: astore_1
       8: ldc           #4                  // String hello
      10: astore_2
      11: ldc           #5                  // int 100000000
      13: istore_3
      14: aload_1
      15: invokevirtual #6                  // Method Test.add:()I
      18: pop
      19: return
}
  • '#n'으로 표현된 것이 constant pool의 인덱스를 뜻한다.

References

JVM Internals

Stack

Call Stack

JVM stack과 frame

Inside JVM 2ed

The Structure of the Java Virtual Machine

추천영상 (위 내용을 20분 영상으로 요약한 것. 그림으로 동작하는 원리를 설명함)

'자바' 카테고리의 다른 글

간단한 ObjectMapper 만들어보기 (Java Reflection API)  (0) 2022.09.15