Stack?
- 스택은 자료를 넣고(Push), 가장 최근에 넣은 자료를 뺄 수 있는(Pop) 동작을 가진 Abstract Data Type(ADT)이다.
- 이처럼 먼저 넣은 자료가 나중에 나오는 것을 LIFO(Last In, First Out) 구조라고 한다.
Call Stack?
- 컴퓨터의 서브루틴(== 프로시저 == 함수 == 메소드) 정보를 저장하기 위해 사용하는 스택 자료구조이다.
- Control Stack, Runtime Stack, Execution Stack, Stack 라고도 불린다.
- P1 -> P2를 호출할 때 넘기는 파라미터와 P1 리턴주소를 Call Stack에 Push한다. (P2->P3도 동일)
- P2 -> P1로 리턴할 때 P2는 자기 자신의 local variable만 Pop을 하고 파리미터는 다음 제어권을 가진 P1이 Pop한다.
- 이 구조는 CPU, 기계어 아키텍처마다 다르다. 제어권을 넘기기 전에 파라미터를 Pop할 수도 있다.
JVM에서 Stack, Frame
1. 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;
}
}
- 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 리턴 |
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
'자바' 카테고리의 다른 글
간단한 ObjectMapper 만들어보기 (Java Reflection API) (0) | 2022.09.15 |
---|