JVM Stack 메모리란
스레드의 수행 정보를 기록하는 Stack Frame들을 저장하는 메모리 영역. 스레드마다 하나씩 존재하고, 스레드가 시작할 때 생성된다. 스레드가 자바 메소드를 수행할 때 JVM은 Stack Frame을 하나 생성해서 JVM Stacks에 푸시한다. Stack Frame 중 현재 수행하고 있는 메소드의 정보를 저장하는 것을 Current Frame이라고 한다. 직전에 Push된 Stack Frame이 Current Frame이 되며, 수행을 마친 메소드는 Pop된다. 만약 메소드가 비정상 종료를 하게 되면 Exception을 처리 후 Pop된다. 여기서 설명되는 JVM Stacks는 JVM 명세(Specification)의 내용에 지나지 않으며, JVM Stacks의 실제 구현은 벤더가 구현하기 나름이다.
Stack Frame은 메소드가 호출될 때마다 메소드 당 하나씩 생성되고, 해당 메소드 호출이 완료되면 없어진다. 컴파일 시에 클래스의 메타 정보들 (메소드 내에서 사용하는 변수나 연산, 반환 값의 타입 등)을 참고하여 크기가 결정된다. 각 Stack Frame은 1) Local Variable Section, 2) Operand Stack, 3) Frame Data 세 부분으로 구성되어있다.
1) Local Variable Section이란
Local Variable Section은 위 그림과 같이 메소드의 파라미터 변수와 지역 변수들을 저장한다. Local Variable Section은 Array로 구성되어있고, 인덱스를 통해 데이터에 접근할 수 있다. 원시형 변수들은 고정된 크기로 저장되고, 참조형 변수들은 Heap 메모리에 저장되며 그 주소만 Local Variable Section에 저장된다.
Local Variable Section의 0번째 인덱스
Instance Method에 대한 Stack Frame인 경우 Local Variable Section Array의 0번째 인덱스에는 반드시 클래스 인스턴스의 참조값이 저장되게 되어있는데, 이 참조값을 따라 Heap에 있는 Class 인스턴스의 데이터를 찾을 수 있다. Instance Method가 아닌 Static Method의 경우에는 인스턴스에 속한 것이 아니라 클래스 자체에 속해 있는 것이기 때문에 0번째 인덱스의 참조 값이 존재하지 않는다.
2) Operand Stack이란
JVM은 연산을 위해 사용되는 데이터 및 그 결과를 Operand Stack에 집어넣고 프로그램을 수행한다. Operand Stack은 JVM의 작업 공간이라고 할 수 있다. OperandStack과 Local Variable Section이 실제로 다음 함수를 처리하는 과정을 도식화 하면 아래 그림과 같다.
class JvmInternal2{
public void operandStack() {
int a, b, c;
a = 5;
b = 6;
c = a + b;
}
}
Operand Stack을 보면 JVM이 Operand Stack 연산에 필요한 데이터 5, 6과 연산의 결과인 11에 저장 했다가 지우는 것을 확인할 수 있다.
2) Frame Data란
Frame Data는 (1) Dynamic Linking (=Constant Pool Resolution) (2) Method가 정상 종료했을 때의 return (3) 비정상 종료를 했을 시에 Excpetion dispatch 세 가지 행위를 위한 정보들을 말한다.
(1) Dynamic Linking (Constant Pool Resolution)을 위한 정보 - Constant Pool의 포인터
Frame은 JVM이 Constant Pool에 있는 데이터를 참조해서 Symbolic Reference를 resolve 할 수 있도록 Constant Pool에 대한 포인터를 저장하고 있다. JVM은 이 포인터를 이용하여 필요할 때마다 Constant Pool을 참조한다.
2) 메소드가 성공 후 return을 위한 정보 - 자신을 호출한 Stack Frame의 Instruction Pointer
메소드가 종료되면 JVM Stacks에서 해당 Stack Frame이 pop 되면서 자신을 호출한 메소드가 Current Frame이 된다. Frame Data에 자신을 호출한 Stack Frame의 Instruction Pointer가 있다. 메소드가 종료되면 JVM은 이 정보를 PC Register에 저장하고 Stack Frame에 빠져나간다. 반환 값이 있으면 반환 값을 다음 번 Current Frame의 Operand Stack에 푸시한다.
3) 메소드 비정상 종료 시 exception dispatch를 위한 정보 - 메소드의 Exception Table에 대한 Reference
클래스 파일에는 Exception Table이라는 섹션이 있는데, 다음 JvmInternal12 클래스에 대한 바이트 코드(Exception Table 포함) 은 다음과 같다.
class JvmInternal2 {
public void operandStack() {
int a,b,c;
a = 5;
b = 6;
try {
c = a+b;
} catch (NullPointerException e) {
c = 0;
}
}
public void operandStack ();
Code:
0: iconst 5
1: istore 1
2: bipush 6
4: istore 2
5: iload 1
6: iload 2
7: iadd
8: istore 3
9: goto 16
12: astore 4
14: iconst 0
15: istore 3
16: return
Exception table:
from to target type
5 9 12 Class java/lang/NullPointerException
JVM은 try 블록 내에서 Exception이 발생하거나 throw 되면 그게 Exception Table의 type 컬럼에 정의된 것과 같은지 비교한다. 만약 일치하면 Exception Table의 target으로 점프하여 수행하고, 일치하지 않으면 Current Frame을 종료하고 이 메소드를 호출한 메소드의 Stack Frame에 이 Exception을 던진다.
참고 자료
- Oracle JVM Specification - Stack Frame
- Bill Venners - "Java Virtual Machine's Internal Architecture"
- 「 Java Performance Fundamental 」2009, 김한도
'JVM' 카테고리의 다른 글
클래스로더 메모리 누수 원인과 해결법 (ThreadLocal 사용시 주의점) (1) | 2025.02.18 |
---|---|
Java 클래스 로딩 과정 (1) | 2024.12.09 |
Method Area 정의와 구성 (3) | 2024.10.18 |
JVM이란? - JDK vs. JRE vs. JVM (0) | 2024.10.16 |