JVM의 메모리 사용 구조는 크게 Thread Memory 영역과 Shared Memory 영역으로 나뉜다고 볼 수 있다.
Thread 영역
1. Stack Area
Stack Area에는 Local Variable Array, Operand Stack, 프레임(Frame Data)이 저장된다.
1-1. Local Variable Array
메서드의 지역 변수들을 Array에 저장한다. 다음과 같은 코드가 있다고 하자.
class Test {
void hello(int a, double b, String s) {
return;
}
}
Local Variable Array는 다음 변수들을 저장한다.
+-----------+
0 | reference | this (hidden)
+-----------+
1 | int | int a
+-----------+
2 | | double b
+ double +
3 | |
+-----------+
4 | reference | String c
+-----------+
원시 타입은 값 그대로 저장되는 반면, 참조 타입은 원본 객체의 reference 값만 저장한다. 원시 타입 사용이 조금 더 빠른 이유 중 하나이다. 메모리 사용 관점에서도 참조 타입은 64bit JVM을 기준으로 8바이트의 메모리를 가지는 반면에 원시 타입은 1~8 바이트까지 용도에 맞게 메모리를 사용하므로 더 효율적이다.
1-2. Operand Stack
오퍼랜드 스택은 메서드 내 계산을 위한 JVM의 작업 공간이다. 예를 들어 다음과 같은 코드가 있다고 하자.
class Main {
public int test() {
int a = 4;
int b = 3;
return a + b;
}
}
컴파일하고 javap 로 바이트 코드를 확인할 수 있다.
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
}
Code 부분에 있는 Java Bytecode Opcode가 JVM에 의해서 수행됨을 알 수 있다. 더 많은 Opcode는 이곳에서 볼 수 있다. Opcode 순서에 의해서 연산이 이루어지는 것으로 보이는데 이 연산은 Push, Pop 작업을 통해 수행되며 필요할 때마다 Array에 추가적인 공간을 할당한다. (내부 구현은 Array이므로)
1-3. Frame Data (Stack Frame)
스택 프레임은 런타임 상수 풀 Resolution과 메서드의 종료 정보, 그리고 비정상 종료시 발생하는 Exception 관련 정보들을 가진다.
런타임 상수 풀 Resolution 이란 Symbolic Reference로 표현된 Entry를 찾아 Direct Reference로 변경하는 과정을 의미한다. Class의 모든 Symbolic Reference는 Method Area의 Constant Pool에 있는 상수를 참조하기 때문에 Constant Pool Resolution이라고 부르는 것이다.
런타임 상수 풀이란 런타임에 생성되는 상수 저장소이다. Java7 이전에는 JVM에 의해서 고정된 메모리 크기를 갖는 Permanent라는 영역에 상수 풀이 저장되었었다. 하지만 고정된 메모리 크기라는 한계와 상수 추가로 인한 OutOfMemory 발생의 여지 때문에 Java8에서 Heap 영역으로 이동하게 되었다. Constant Pool은 다룰 내용이 많기 때문에 추후 Shared Memory를 다루면서 설명하도록 한다.
프레임은 자신을 호출한 프레임의 포인터를 가지고 있다. 그래서 메서드가 종료되면 JVM은 이 정보를 PC Register에 설정하고 Stack 프레임을 빠져나가는데, 만약 반환 값이 있다면 해당 값을 자신을 호출한 Method의 Stack 프레임의 Operand Stack에 다음 연산을 위하여 Push 하는 작업을 병행한다. 그러나 메서드가 비정상 종료되어 Exception이 발생하였다면 JVM은 각 Class File에 존재하는 Exception Table을 참조하여 Catch 절에 해당하는 바이트 코드로 점프한다.
2. PC Register
Thread가 생성될 때마다 생성되는 영역으로 프로그램 카운터, 즉 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. CPU 내부에 Program Counter 역할과 비슷해보인다.
3. Native Method Area
이 영역이 무엇인지 알려면 Native Method에 대해서 알아야 한다. 네이티브 메서드는 JNI에 의해서 탄생한 개념이다. JNI는 Java Native Interface로 Java 이외의 언어로 만들어진 애플리케이션이나 라이브러리가 Java 프로그램과 상호작용할 수 있도록 연결시켜주는 인터페이스이다. 즉 Java와 다른 언어를 연동하는 솔루션인 것이다. 그럼 네이티브 메서드의 주요 쓰임을 알아보자.
네이티브 메서드의 주요 쓰임은 레지스트리나 파일 락과 같은 플랫폼에 특화된 기능을 사용할 수 있다. 하지만 자바 버전이 올라가면서 필요성이 줄어들고 있다고 한다. 특히 Java9 에서는 Process API가 추가되어 OS 프로세스에도 접근할 수도 있다.
정말로 대체할만한 자바 라이브러리가 없다면 네이티브 라이브러리 사용을 고려해야 하지만 단순히 성능 개선을 목적으로 네이티브 메서드 사용을 권장하지는 않는다고 한다.
그럼 Native Method Area는 무슨 공간이지?
우선 이 영역은 가장 흔히 사용되는 Hotspot JVM, IBM JVM 에서 따로 구분 짓지 않고 있다. 개념적으로 분리된 공간이라고 보는 것이며 단지 Java Method를 수행하는 것이냐 Native Method를 수행하는 것이냐를 따져 프레임이 기본 Java Stack Frame인지 아니면 Native Stack Frame인지를 구분할 뿐이라고 한다. 즉 모든 프레임은 JVM Stack 내에 함께 저장되고 Native Method를 수행하는 경우 Java Stack Frame에서 Native Stack Frame으로 작업이 전환된다고 보는 것이 좋을 것 같다.
Ref
https://songiam.tistory.com/74
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=2000yujin&logNo=130156226754
https://johngrib.github.io/wiki/jvm-stack/
'JAVA' 카테고리의 다른 글
[기본 시리즈] java.io 자바 기본 네트워킹 TCP/IP (0) | 2022.09.06 |
---|---|
[기본 시리즈] java.io 입력 스트림과 출력 스트림 (with 보조 스트림) (0) | 2022.09.02 |
[기본 시리즈] 해시 테이블과 해시 충돌 그리고 JAVA의 HashMap (0) | 2022.08.18 |
[기본 시리즈] JAVA 특징 및 JVM에 대하여 (0) | 2022.08.17 |
[컬렉션 프레임워크 끝내기] #List (0) | 2022.07.19 |