stack과 frame?

  • JVM 스레드가 생겨날 때, 해당 스레드를 위한 스택(stack)도 같이 만들어진다.1
    • 그 스택엔 무엇이 들어가는가? 프레임(frame)이 들어간다.
  • 그렇다면 프레임은 무엇인가?
    • 프레임은 메소드가 호출될 때마다 만들어지며, 메소드 상태 정보를 저장한다.
Thread1   Thread2   Thread3
+-------+ +-------+ +-------+
|       | |       | |       |
|       | |       | | frame |
|       | |       | | frame |
| frame | |       | | frame |
| frame | | frame | | frame |
+-------+ +-------+ +-------+
  • 스레드가 쓸 수 있는 스택의 사이즈를 넘기게 되면 StackOverflowError가 발생한다.
  • 스택 사이즈를 동적으로 확장할 수도 있는데 확장할 메모리가 부족하거나, 새로운 스레드를 만들 때 필요한 새로운 스택에 할당할 메모리가 부족하면 OutOfMemoryError가 발생한다.

frame

그리고 프레임은 다음의 3가지로 구성되어 있다.

  • Local Variables
  • Operand Stack
  • Constant Pool Reference

Local Variables

로컬 변수 배열은 메소드의 지역 변수들을 갖는다.

다음과 같은 자바 코드가 있다고 하자.

class Test {
    public int hello(int a, double b, String c) {
        return 0;
    }
}

프레임의 로컬 변수 배열은 다음과 같이 만들어진다.

  +-----------+
0 | reference | this (hidden)
  +-----------+
1 | int       | int a
  +-----------+
2 |           | double b
  + double    +
3 |           |
  +-----------+
4 | reference | String c
  +-----------+
  • reference는 heap의 레퍼런스를 의미한다.
  • primitive 타입은 값을 그냥 프레임에 저장한다.
    • 그래서 int나, doubleInteger, Double보다 조금 더 빠르다.
  • double, long은 두 칸씩 차지한다.

Operand Stack

오퍼랜드 스택은 메소드 내 계산을 위한 작업 공간이다. 어셈블리어나 아희, 브레인퍽 같은 언어를 다뤄봤다면 어렵지 않게 이해할 수 있다.

예를 들어 다음과 같은 4+3을 계산하는 자바 코드를 작성했다고 하자.

package main;

class Main {
    public int test() {
        int a = 4;
        int b = 3;
        return a + b;
    }
}

컴파일한 다음, 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: iconst_4
       1: istore_1
       2: iconst_3
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: ireturn
}

여기에서 주목할 것은 public int test()Code 부분이다.

위에서 작성한 test 메소드의 계산 내용이 바이트코드로 변환된 것을 볼 수 있다.

하나하나 살펴보자.

바이트코드 작동 자바 코드
0: iconst_4 상수 4를 오퍼랜드 스택에 push. 4
1: istore_1 pop 하여 지역 변수 배열 1번 인덱스(int a)에 저장. int a = 4
2: iconst_3 상수 3을 오퍼랜드 스택에 push. 3
3: istore_2 pop 하여 지역 변수 배열 2번 인덱스(int b)에 저장. int b = 3
4: iload_1 지역 변수 배열 1번 인덱스의 값을 읽는다. a
5: iload_2 지역 변수 배열 2번 인덱스의 값을 읽는다. b
6: iadd 읽은 두 값을 더한다. a+b
7: ireturn 더한 값을 리턴한다. return a+b

Constant Pool Reference

프레임은 런타임 상수 풀의 참조를 갖는다.2

궁금해진다. 위의 자바 코드를 다음과 같이 수정해 보았다.

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";
}

컴파일하고, 바이트코드를 읽어보면 0: ldc #25: ldc #4를 볼 수 있다.

$ 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
}

ldc는 무엇을 하는 instruction일까?

문서를 찾아보니의 기능은 다음과 같다.

Push item from run-time constant pool

ldc의 신택스는 심플하게 ldc index로 이루어다. 즉 뒤의 #2, #4가 바로 상수 풀의 인덱스를 의미한다.

The index is an unsigned byte that must be a valid index into the run-time constant pool of the current class. The run-time constant pool entry at index either must be a run-time constant of type int or float, or a reference to a string literal, or a symbolic reference to a class, method type, or method handle.

한편 위쪽의 1: invokespecial #1도 눈에 들어온다. invokespecial찾아보자.

Invoke instance method; special handling for superclass, private, and instance initialization method invocations

invokespecial의 신택스는 invokespecial indexbyte1 indexbyte2로 이루어진다.

그리고 조금 더 설명을 읽어보면 invokespecial 뒤의 #1도 상수 풀의 인덱스임을 알 수 있다.

The unsigned indexbyte1 and indexbyte2 are used to construct an index into the run-time constant pool of the current class, where the value of the index is (indexbyte1 « 8) &vert; indexbyte2.

참고문헌

주석

  1. Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames. 출처 

  2. Each frame contains a reference to the run-time constant pool for the type of the current method to support dynamic linking of the method code. 출처