1. 450│2013 기술백서 White Paper
Runtime Data Areas
㈜엑셈 컨설팅본부/APM팀 임 대호
Runtime Data Area 구조
Runtime Data Area 는 JVM 이 프로그램을 수행하기 위해 할당 받는 메모리 영역이라고 할 수
있다. 실제 WAS 성능 문제에 직면했을 때, 대부분의 문제점은 Runtime Data Area 에서 발생
하는 경우가 많다. Memory Leak 이나 Garbage Collection 인 경우가 그 예이다. 이러한 성능
문제가 발생할 시 이 문제가 왜 발생한 것인지 어디에서 발생한 것인지 확인하는 것은 쉽지 않다.
이러한 경우 Runtime Data Area 의 아키텍처를 공부하는 것이 해당 문제를 분석하는데 큰 도
움이 될 것이다.
Runtime Data Area 는 크게 다섯 가지 영역으로 나뉘게 된다. 그것은 PC Register, Java
Virtual Machine Stacks, Native Method Stacks, Method Area, Heap 이다. 각 Thread 별로
생기는 영역은 PC Register, Java Virtual Machine Stacks, Native Method Stacks 이고, 모든
Thread 가 공유하는 영역은 Method Area 와 Heap 이다.
[그림 1] Runtime Data Area
2. Part 2 APM │451
PC Register
Java 의 PC Register 는 CPU 내의 기억장치인 레지스터와는 다르게 작동한다. Register-Base
가 아닌 Stack-Base 로 동작한다. PC Register 는 각 Thread 별로 하나씩 존재하며 현재 수행
중인 Java Virtual Machine Instruction 의 주소를 가지게 된다. 만일 Native Method 를 수행
한다면 PC Register 는 Undefined 상태가 된다. 이 PC Register 에 저장되는 Instruction 의 주
소는 Native Pointer 일 수도 있고 Method Bytecode 일 수도 있다. Native Method 를 수행할
때에는 JVM 을 거치지 않고 API 를 통해 바로 수행하게 된다. 이는 Java 는 Platform 에 종속 받
지 않는 다는 것을 보여준다.
Java Virtual Machine Stacks
Java virtual Machine Stacks 은 Thread 의 수행 정보를 Frame 을 통해서 저장하게 된다.
Java Virtual Machine Stacks 는 Thread 가 시작될 때 생성되며, 각 Thread 별로 생성이 되기
때문에 다른 Thread 는 접근할 수 없다. 대표적으로 Local variable 를 가진다.
[그림 2] Java Virtual Machine Stacks
3. 452│2013 기술백서 White Paper
Java Virtual Machine Stacks 에서 현재 수행하고 있는 Frame 을 Current Frame 이라고 한다.
Stack Frame 에는 Method 의 Parameter variable, Local variable 과 연산 결과를 저장하게
된다.
Stack Frame
Stack Frame 은 Thread 가 수행하고 있는 Method 단위를 기록하는 곳이다. Method 의 상태
정보를 저장하는 Stack Frame 은 크게 세가지 영역으로 나뉜다. 그것은 Local Variable
Section, Operand Stack, Frame Data 이다. Stack Frame 의 크기는 컴파일 때 결정이 난다.
Local Variable Section
Local Variable Section 은 Method 의 Parameter Value 와 Local Variable 을 저장한다. Local
Variable Section 은 0 부터 시작하는 array 형 인덱스를 가지는데 parameter value 부터 할당
된다. Local Variable 은 순서가 정해져 있지 않다. 또한 만약 사용되지 않으면 할당되지 않을 수
도 있다. Parameter Value 와 Local Variable 가 Primitive Type 인 경우(ex int 형)는 고정된
크기로 할당된다. 하지만 String 이나 Array 같은 객체는 가변크기이므로 Reference 를 갖는다.
Java source
class Test {
public int testMethod (int a, char b, long c, float d, object e, double f, String g, byte h,
short I, Boolean j) {return 0; }
4. Part 2 APM │453
[그림 3] Local Variable Section에서의 Method Parameter
testMethod 는 10 개의 Parameter 변수를 가진다. Local Variable Section 에서는 그림 6 과
같이 할당 받게 된다. 여기서 눈 여겨 봐야 할 것은 long 형과 double 형의 경우 array 를 두 개
씩 사용한다는 것이다. char, byte, short, boolean 형으로 선언한 것은 Local Variable Section
에는 모두 int 형으로 할당 된다는 것이다. 만약 변수를 선언할 때 Inteager 형과 int 형 중 어느
것이 성능에 더 유리할 것인가? int 형이 성능에 더 유리하다 그 이유는 int 형은 Java Virtual
Machine Stack 에 저장되는 반면 Inteager 형은 Heap 에 저장이 되기 때문에 (reference)
Heap 까지 찾아가는 비용이 더 들게 되기 때문이다.
0 번 Entry 의 hidden this 는 선언한 적이 없는 변수임에도 불구하고 0 번 Entry 에 저장이 되어
있다. Hidden this 는 Heap 에 있는 Class 의 Instance 에 대한 reference 이다.
5. 454│2013 기술백서 White Paper
Operand Stack
Operand Stack 은 JVM 의 작업 공간이다. 그 이유는 JVM 이 연산에 필요한 데이터와 연산 결
과를 Operand Stack 에 넣고 처리하기 때문이다. 작동 방식은 하나의 Instruction 이 연산을 위
해 Operand Stack 에 값을 넣으면 다음 Instruction 에서는 이 값을 빼서 사용하게 된다. 연산
의 결과 역시 Operand Stack 에 저장된다. Operand Stack 역시 Array 로 구성되어 있으며
Stack 의 구조로 Push, Pop 작업을 수행한다.
Frame Data
Stack Frame 을 구성하고 있는 영역이다. 이 곳에서는 Constant Pool Resolution 정보와
Method 가 정상 종료 했을 때의 정보 또는 비정상 종료를 했을 때 발생하는 Exception 정보를
저장하고 있다. Resolution 이란 Symbolic Reference 를 JVM 에서 실제 접근할 수 있는 Direct
Reference 로 변경하는 것을 말한다. Symbolic Reference 는 Method Area 의 Constant Pool
에 저장된다. Frame Data 에 저장된 Constant Pool Resolution 은 Method Area 의 Constant
Pool 의 Pointer 이다. JVM 은 필요할 때마다 Pointer 로 Constant Pool 에 접근한다. 보통 상수
뿐만 아니라 다른 Class 를 참조하거나, Method 를 접근할 경우에도 Constant Pool 을 참조해
야 한다. 이는 Java 의 모든 Reference 과 Symbolic Reference 인 것과 관련이 있다.
Method 가 수행이 되고 종료하는 시점에 Java Virtual Machine Stack 에서 해당 Stack Frame
이 Pop 되어 사라진다. 이때 이전에 Method 를 찾아가야 하는데 Frame Data 에는 이전에 자신
을 호출한 Stack Frame 의 Instruction Pointer 가 들어가있다.
만약 Method 가 exception 을 발생시키면 해당 exception 을 핸들링 해줘야 하는데 이
exception 정보가 Frame Data 에 저장이 된다.
Native Method Stacks
6. Part 2 APM │455
JVM 은 Native Method 를 위해 Native Method Stack 이라는 메모리 공간을 가진다.
Application 에서 Native Method 를 호출하게 되면 Native Method Stack 에 새로운 Stack
Frame 을 생성하여 Push 한다. 이는 JNI 를 이용해 JVM 내부에 영향을 주지 않기 위함이다.
Native Method 의 수행이 끝나면 해당 Method 를 호출한 Stack Frame 이 아닌 새로운 Stack
Frame 을 하나 생성하여 작업을 수행한다.
[그림 4] Native Method 수행과 Stack
우리가 자주 사용하는 Hotspot JVM 이나 IBM JVM 에서는 두 Stack 영역을 구분 두지 않는다.
모두 Native Stack 으로 통합이 되어 있는데 이는 JVM 에서 사용하는 Thread 가 Native
Thread 인 것과 관계가 깊다.
Hotspot JVM 에서 Stack Size 를 조정하는 옵션은 –Xss 와 –Xoss 두 가지 인데, -Xss 는
Native Stack Size 를 조정하는 옵션이고 –Xoss 는 Stack Size 를 조정하는 옵션이다. Hotspot
JVM 에서 Stack Size 조정은 -Xss 만을 통해서 하도록 되어 있다.
7. 456│2013 기술백서 White Paper
Method Area
Method Area 는 모든 Thread 가 공유하는 메모리 영역이다. Method Area 는 Class 와
Interface 의 Bytecode 및 메타 데이터가 저장이 된다. 이는 Gabage Collection 의 대상이 되
며 Hotspot JVM 의 경우 Permenent Area 라는 명칭으로 사용된다. IBM JVM 같은 경우는
Heap 내에 Class Object 형태로 저장된다.
Constant Pool
Constant Pool 은 JVM 에서 가장 중요한 역할을 수행하는 곳이고 많이 사용되는 곳이기도 하다.
Constant Pool 에는 Literal Constant 는 물론이고 Filed(Member Variable, Class Variable),
Method 로의 모든 Symbolic Reference 까지 가진다. Symbolic Reference 의 역할을 하는 곳
이 Constant Pool 이다.
Field Information
Field Information 은 모든 Field 의 정보가 있다.
Field 이름
Field 의 Data Type, 선언 순서
public, private 와 같은 Field 의 Modifier
Method Information
Method Information 은 모든 Method 정보를 갖게 된다.
Method 이름, Method 반환 값의 Data Type 과 void
Method parameter 수와 Data Type, 선언 순서, Method 의 Modifier
8. Part 2 APM │457
만약 Method 가 native 나 abstract 가 아니라면 다음의 정보가 추가된다.
Method 의 Bytecode
Method Stack Frame 의 Operand Stack 및 Local Variable Section 의 크기
Exception Table
Class Variable
Class Variable 은 static 으로 선언한 변수이며 Method Area 에 저장된다. 이 변수는 모든
Instance 에서 접근 가능하기 때문에 동기화 이슈가 발생할 수 있다. Class Variable 을 final 로
선언할 경우 이는 Constant Pool 에 Literal Constant 로 저장된다.
Method Table
Method Area 에 저장되는 정보는 Heap 에 Object 를 생성할 때 사용되기도 하나 Reference 를
위한 Data 로서의 역할도 중요하다. Java 에서는 Reference 로 객체를 찾아 다니는 일은 속도
측면에서 굉장히 중요하다.
Method Table 은 Class 의 Method 에 대한 Direct Reference 를 가진다고 보면 된다. Method
Table 을 이용해 Super Class 에서 상속된 Method 의 Reference 까지 확인이 가능하다.
Java Heap
Java Heap 에서 많은 성능 이슈가 발생하고 있다. Gabage Collection 에 의한 것이 대표적이
예이다. Java Heap 은 Instance 와 Array 객체 두 종류가 저장되는 공간이다. Java Heap 은 모
든 Thread 에 의해 공유된다고 했는데 그로 인해 동기화 문제가 발생할 수 있다. Java Heap 은
Gabage Collection 이란 Mechanism 에 의해 메모리 해제 작업은 필요하지 않다. 실제 많이 사
용하고 있는 Hotspot JVM 과 IBM JVM 의 Object 의 구조를 살펴보자.
9. 458│2013 기술백서 White Paper
Object 구조
Heap 에 저장되는 Object 와 Array 객체는 Head 와 Data 로 나뉘게 된다.
Hotspot Object 구조
Hotspot JVM 의 Object 의 경우 두 개의 Header 를 가지고 있고, Array 객체의 경우 세 개의
Header 를 갖는다.
[그림 5] HotSpot Object 구조
First header 는 Mark Word 라 불리며 Gabage Collection 과 Syncronization 작업을 위해 사
용한다. 두 번째 Header 에는 Method Area 의 Class 정보를 가리키는 Reference 정보가 저장
된다. Array 의 경우 세 번째 Header 가 존재하게 되는데 Array Size 정보를 갖기 위한 공간이
다.
IBM Object 구조
IBM JVM 은 Java 5 의 경우를 살펴보겠다. Object 의 경우 2 개의 Header 를 가지고 Array 의
경우 Array Size 정보를 담고 있는 Header 를 포함해 총 3 개의 Header 를 가지게 된다.
10. Part 2 APM │459
[그림 6] IBM Object 구조 (java 5)
Vtable 은 Object Information 에 대한 Vtable Pointer 를 가지고 있고 이는 주로 Garbage
Collector 에 의해 사용된다. 두 번째 Header 는 Lock Word 를 이용해 Object 의 Lock 획득 유
무를 확인하기 위해 사용된다.
Heap의 구조
Hotspot JVM Heap 구조
Hotspot Heap 의 가장 큰 특징은 Young Generation 과 Old Generation 으로 나뉘어 진다는
것이다.
[그림 7] Hotspot JVM의 Heap 구조
11. 460│2013 기술백서 White Paper
Young Generation 은 Eden 과 Survivor 영역으로 구성되어 있고 최초 Heap 에 객체가 할당되
는 곳은 Eden 이다. 그 이후 Object 참조 여부를 확인해서 참조가 되어 있는 상태면 Survivor
영역으로 넘기고 오래 살아 남으면 Old 영역으로 이동시킨다. 이를 Promotion 이라고 한다. 참
고로 Young Generation 에서 일어나는 GC 를 Minor GC 라고 하고 Old Generation 에서 일어
나는 GC 를 Major GC 라고 한다.
IBM JVM Heap 구조
IBM JVM Heap 은 버전에 따라 다른 모습을 보이고 있다. 먼저 IBM JVM 1.4 를 살펴보도록 하
겠다. 1.4 버전의 대표적 특징은 One-Heap 이라는 점이다.
[그림 8] IBM JVM 1.4 Heap 구조
K Cluster 와 P Cluster 는 Hotspot JVM 의 Permanent Area 와 같은 역할을 한다고 보면 된다.
K Cluster 가 Method Area 의 정보를 가지고 있는 Class Object 를 저장하는 공간인 반면 P
Cluster 는 Pinned 상태의 Array 나 일반 Ojbect 를 저장한다. Cache 영역은 Thread 마다 Lock
없이 할당할 수 있는 공간이다. LOA 는 Large Object 를 위한 공간이다. 이 Heap 은 Garbage
Collection 과 객체에 빠른 할당을 하기 위해 Allocbits 와 Markbits 라는 Bit Vector 와 Freelist
를 가진다.
12. Part 2 APM │461
[그림 9] Heap과 Allocbits, Markbits, Freelist
Allocbits 는 Object 의 시작점에 Bit 가 On 이 된다. Allocbits 는 단순히 할당 여부만 알 수 있고
Garbage Collection 의 대상인지는 판단할 수 없다. Object 의 Live 는 Markbits 를 통해 알 수
있다. Cache Alloction 을 사용하여 Thread 가 Object 를 할당할 경우는 Thread 에게 할당 된
Heap(TLH)을 모두 사용한 후에나 Allocbits 에 On 으로 표시된다. 그리고 Garbage Collection
에 의해 Free space 가 되면 다시 Off 로 된다.
Freelist 는 Object 를 위해 Heap 공간을 할당 하기 위한 자료구조로 보면 된다. 이 Freelist 는
linked List 구조로 되어 있으며 마지막 Chunk 의 Next Filed 는 Null 이다. Object 에 Heap 을
할당하기 위해 Freelist 를 탐색 하고 맞는 Chunk 가 없으면 다음 Free Chunk 를 찾을 때까지
Jump 를 반복하게 된다. 이때 사용하고 남은 Chunk 역시 Freelist 로 등록되고 만약 Chunk 의
Size 가 512 Byte 미만이면 이는 Compaction 의 대상이 된다. 이렇게 작은 Free Chunk 를
Dark Matter 라고 한다. 1.4 버전부터 sub pool 을 제공하는데 이는 Freelist 를 크기 별로 다양
하게 제공하는 장점 있다. 기존 방법이 First Fit 이었다면 sub pool 방식은 Best Fit 을 추구한다.
IBM JAVA 5 Heap 구조
JAVA 5 에서는 기존에 버전과 달리 두 가지 변화가 생긴다. 하나는 System Heap 을 포함하지
않는 다는 것이고 다른 하나는 Hotspot JVM 과 같이 Generation Heap 을 사용할 수 있다는 것
이다. 단 옵션을 적용할 때만 사용이 가능하다. 옵션은 다음과 같다.
13. 462│2013 기술백서 White Paper
-Xgcpolicy:gencon
Hotspot JVM 에서 Young Generation 은 Nursery Generation 이고, Old Generation 은
Tenured Generation 이다
[그림 10] IBM JAVA 5 의 Generational Heap
Nursery Area 는 Allocation Space 와 Survivor Space 로 나뉘게 되는데, Allocation Space
는 Object 가 최초로 할당 되는 곳이고 Survivor Space 는 Allocation Space 가 가득 차거나
Allocation Failure 가 발생하면 이동되는 곳이다. Tenured Space 는 Nersery Generation 의
성숙한 Object 들이 Promotion 하는 곳이다.
결론
Runtime Data Areas 는 객체의 생성과 소멸이 반복되는 곳이다. 그로 인해 성능 이슈가 빈번한
곳이기도 하다. 지금껏 Runtime Data Areas 에 대해서 알아 보았는데 이는 성능 분석을 위한
기초가 될 것이라고 생각한다. 앞 서 살펴보았듯이 하나를 참조를 위해 Java Stack, Method
Area, Heap 의 전 부분에 Jump 작업을 수행한다는 것을 알고 있을 것이다. 불필요한
Reference 의 남발은 성능을 떨어뜨리는 결과를 만든다. 또한 불 필요하게 Method 의 Depth
역시 깊어지게 되어 Java Stack 에서는 Stack Frame 을 더 많이 생성해야 되고 그에 따라
push, pop 작업이 빈번해 진다. 이렇게 Stack Frame 이 많아지는 것 역시 모두 Resource 낭비
로 이어 지게 된다. 객체와 Primitive Type 차이가 좋은 예가 될 것이다. 앞으로 Runtime Data
Areas 지식을 바탕으로 공부해 나간다면 WAS 성능 전문가의 길도 그리 멀지만은 않을 것이다.