자바
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 구성이 달라진다.
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와 달리 인덱스로 접근할 수 없다.)
값을 저장하는 작업 공간으로 활용된다.
class Main {
void add () {
int a = 100 ;
int b = 98 ;
int c = a+b;
}
}
class Main {
Main();
Code:
0 : aload_0
1 : invokespecial #1
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의 참조를 포함한다.
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;
}
}
class Main {
Main();
Code:
0 : aload_0
1 : invokespecial #1
4 : return
public void test () ;
Code:
0 : new #2
3 : dup
4 : invokespecial #3
7 : astore_1
8 : ldc #4
10 : astore_2
11 : ldc #5
13 : istore_3
14 : aload_1
15 : invokevirtual #6
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분 영상으로 요약한 것. 그림으로 동작하는 원리를 설명함)
공유하기
URL 복사 카카오톡 공유 페이스북 공유 엑스 공유