Runtime Data Area의 구성

코코딩딩·2022년 4월 17일
2

JVM

목록 보기
1/1
post-thumbnail

🎯 목표

🚀 JVM의 Runtime Data Area의 5가지 구성에 대해서 알아봅니다.
🚀 각 구성의 특징을 이해합니다.


🙊 JAVA의 정신으로 부터...

💡 자바는 "Write Once, Run Everywhere"라는 철학으로 만들어진 프로그래밍 언어입니다.
💡 즉, 플랫폼 독립성을 목표로한 언어입니다.
💡 자바에서는 이를 4가지 연관 기술을 통해 실현합니다.

  • JAVA Programming Language
  • JAVA Class File Format
  • Java Application Programming Interface(Java API)
  • Java Virtual Machine(JVM)

이 문서에서는 Java Virtual Machine, 그 안에서도 Runtime Data Area을 살펴보겠습니다.

Runtime Data Area

  • Runtime Data Area는 JVM이 프로그램을 수행하기 위해 OS로부터 할당 받은 메모리 영역입니다.
  • 세부 구현은 밴더마다 다를 수 있습니다.
  • Rumtime Data Area는 Heap Area, Metaspace(MethodArea), JVM Stack, Native Method Stack, PC Register로 구성 됩니다.

1. Heap Area

🔎 정의

-Instance, 배열이 저장되는 공간입니다.

🔎 특징

  • JVM이 구동 시에 생성되고, 종료 시에 사라집니다.
  • 모든 Thread에서 접근이 가능합니다.
  • 동기화 이슈가 발생할 수 있습니다.

🔎 구조

Young Generation

👉 Eden : Object가 최초로 할당되는 영역입니다.
해당 영역이 꽉차면 Manor GC가 발생하는데 이때 살아있는 객체는 Survivor1영역으로 이동합니다.
죽은 객체는 남겨놨다가 모든 살아있는 객체가 Survivor1영역으로 이동하면 Eden영역을 비웁니다.

👉 Survivor1 & 2 : Eden에서 살아남은 객체가 머무는 영역입니다.
살아있는 객체를 옴길 때는 반드시 한 Survivor영역은 비워줘야합니다.

Old Generation

👉 Youn Generation에서 오래 살아남은 객체가 이동되는 곳 입니다.
이렇게 이동하는 것을 promotion이라 합니다.
오래 참조될 가능 성이 높은 객체를 모아두는 장소입니다.

* 메모리 구조 외에 GC의 자세한 메커니즘은 Garbage Callection파트에서 다루겠습니다.


2. Metaspace

🔎 정의

  • Class Loader에 의해서 load된 Type(Class, Interface 등)정보, 상수 등을 저장하는 메모리 공간입니다.

🔎 특징

  • JVM이 시작될 때 생성되며, GC 의 대상이 됩니다.
  • 클래스가 로드될 때 할당되고, 클래스가 언로드될 때 GC에 의해서 해제됩니다.
    (Interned String 포함)
  • Hotspot JVM의 경우 Permanent Area라는 명칭으로 특정 메모리영역으로 구분되어 있습니다.

🔎 구성

load된 class는 아래 7개의 정보로 구성됩니다.

Type Information

타입(클래스나 인터페이스)의 전반적인 정보를 담습니다.

  • Full Qualified Name
  • SuperClass Full Qualified Name
  • Class인지 Interface인지 여부
  • Type의 접근지정자

Runtime Constant Pool

: class나 interface에 대한 모든 상수 정보를 가지고 있습니다.
이때 상수란 literal constant를 포함한 type, field, method로의 모든 참조를 말합니다.

  • 하나의 클래스마다 하나의 상수 풀을 가집니다

  • 상수풀은 1~n으로 넘버링 되어있으며 타입, 값에 대한 정보를 담고 있습니다.

  • 아래 바이트 코드 참고해봅시다.

    public class org.jvminternals.SimpleClass
    	SourceFile: "SimpleClass.java"
    	minor version: 0
    	major version: 51
    	flags: ACC_PUBLIC, ACC_SUPER
         	//상수풀!!! 이 부분을 보세요!
    			Constant pool:
    			   #1 = Methodref          #6.#17         //  java/lang/Object."<init>":()V
    			   #2 = Fieldref           #18.#19        //  java/lang/System.out:Ljava/io/PrintStream;
    			   #3 = String             #20            //  "Hello"
    			   #4 = Methodref          #21.#22        //  java/io/PrintStream.println:(Ljava/lang/String;)V
    			   #5 = Class              #23            //  org/jvminternals/SimpleClass
    			   #6 = Class              #24            //  java/lang/Object
    			   #7 = Utf8               <init>
    			   #8 = Utf8               ()V
    			   #9 = Utf8               Code
    			  #10 = Utf8               LineNumberTable
    			  #11 = Utf8               LocalVariableTable
    			  #12 = Utf8               this
    			  #13 = Utf8               Lorg/jvminternals/SimpleClass;
    			  #14 = Utf8               sayHello
    			  #15 = Utf8               SourceFile
    			  #16 = Utf8               SimpleClass.java
    			  #17 = NameAndType        #7:#8          //  "<init>":()V
    			  #18 = Class              #25            //  java/lang/System
    			  #19 = NameAndType        #26:#27        //  out:Ljava/io/PrintStream;
    			  #20 = Utf8               Hello
    			  #21 = Class              #28            //  java/io/PrintStream
    			  #22 = NameAndType        #29:#30        //  println:(Ljava/lang/String;)V
    			  #23 = Utf8               org/jvminternals/SimpleClass
    			  #24 = Utf8               java/lang/Object
    			  #25 = Utf8               java/lang/System
    			  #26 = Utf8               out
    			  #27 = Utf8               Ljava/io/PrintStream;
    			  #28 = Utf8               java/io/PrintStream
    			  #29 = Utf8               println
    			  #30 = Utf8               (Ljava/lang/String;)V
    			{
    			  public org.jvminternals.SimpleClass();
    				Signature: ()V
    				flags: ACC_PUBLIC
    				Code:
    				  stack=1, locals=1, args_size=1
    					0: aload_0
    					1: invokespecial #1    // Method java/lang/Object."<init>":()V
    					4: return
    				  LineNumberTable:
    					line 3: 0
    				  LocalVariableTable:
    					Start  Length  Slot  Name   Signature
    					  0      5      0    this   Lorg/jvminternals/SimpleClass;
    
    			  public void sayHello();
    				Signature: ()V
    				flags: ACC_PUBLIC
    				Code:
    				  stack=2, locals=1, args_size=1
    					0: getstatic      #2    // Field java/lang/System.out:Ljava/io/PrintStream;
    					3: ldc            #3    // String "Hello"
    					5: invokevirtual  #4    // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    					8: return
    				  LineNumberTable:
    					line 6: 0
    					line 7: 8
    				  LocalVariableTable:
    					Start  Length  Slot  Name   Signature
    					  0      9      0    this   Lorg/jvminternals/SimpleClass;
    			}
       
    	
    
    
  • 위의 sayHello 메소드의 바이트코드 3번 라인을 보면 ldc #3 라는 Instruction이 있습니다.

  • 이는 상수풀의 3번째 데이터를 로드 하라는 의미고 옆에 그 데이터가 hello라는 String 데이터라 주석을 달아줍니다.

  • 위로 올라가 상수풀을 직접보면 해당 데이터가 무엇인지 알 수 있습니다.

Field Information

: class에서 선언된 모든 field 정보가 있습니다.

  • field name
  • field data type / 선언순서
  • field의 접근지정자.

Method Information

: class에서 선언된 method의 정보가 저장되는 영역입니다.

  • method name
  • method return type or void
  • method parameter의 수, data type, 선언 순서
  • method 접근지정자

Class Variable

: class 안에서 static 으로 선언된 변수가 저장 되는 영역입니다.

  • 모든 instance에서 공유됩니다.
  • 동기화 이슈가 발생합니다.
  • instance없이 접근 가능합니다.
  • final을 붙이면 constant pool에 저장됩니다.

Reference to class Lcass Loader

: class가 JVM에 의해 load될 때 어떤 Class Loader에 의해서 load되었는지 정보가 저장 되어있습니다.

Reference to Class class

: class가 JVM에 의해서 load되면 항상 java.lang.class 클래스의 instance가 생성됩니다.

  • method area에서는 type 정보의 일부로 이 instance를 Reference 합니다.

Method Table

  • Java는 수 많은 Reference 타입들을 이용하는 언어로 이 reference들을 잘 찾아다닐 수 있도록 하는 것이 중요합니다.
  • 이를 위해서 method table이라는 데이터 구조를 사용합니다.
  • Method Table은 class의 method에 대한 Direct Reference를 갖는 자료구조입니다.
  • 부모 클래스의 메소드에 대한 참조도 가지고 있습니다.

🔎 Java 8이후

  • Java 8 부터는 Metaspace로 명칭이 바뀌고 Native Memory에서 OS에 의해 관리됩니다.
  • Static Object들은(Interned String 포함) Heap에서 관리하고 GC대상이 됩니다.
  • Native Memory는 OS에 의해 자동으로 크기가 관리됩니다.

    참조
    https://openjdk.java.net/jeps/122


3. JVM Stack

🔎 정의

  • 각 Thead의 Stack Frame을 저장하는 메모리 영역입니다.

🔎 특징

  • Thread가 생성될 때 같이 생성됩니다.
  • Thread가 마다 하나씩 생성 됩니다.
  • Stack Frame들로 구성 됩니다.
  • JVM Stack은 Stack Frame을 push하거나 pop하는 작업만 수행합니다.
  • Stack 자료구조와 동일하게 작동합니다.(linear, Last in- First out)

🔎 Stack Frame

: Thread에서 method를 수행 할 때 수행정보를 저장하는 공간입니다.
이 수행 정보는 Local Variable Section, Operand Stack, Frame Data로 구성됩니다.

🔎 Stack Frame 동작

- Thread가 Method를 수행하면 Stack Frame을 생성하여 JVM Stack에 push 한다.
- 컴파일 타임에 사이즈가 정해진다.
- 새로 들어간 Stack Frame이 Current Frame이 된다.
- 해당 Method가 수행을 마치면 Current Frame은 pop된다.
- pop 이후 이전 Stack Frame이 Current Frame이 된다.
- Method 수행 중 Exception이 발생해도 Stack Frame에서 pop된다.

🔎 Stack Frame 구성

Local Variable Section

: 수행하는 Method의 Parameter Variable, Local Variable들을 저장하는 공간입니다.

- Local Variable Section은 배열이다. 0부터 할당되며 index로 접근한다
- instance method는 0에 this를 할당하고, static method는 0부터 Parameter Variable을 할당한다.
- Parameter Variable 순서대로 할당되며, Local Variable은 컴파일러가 알아서 할당한다
- Local Variable은 컴파일러가 최적화 하여 할당 할수도 안할 수도 있다
- Local Variable Section 컴파일 타임 Stack Frame이 사이즈가 정해지면서 같이 사이즈가 정해진다.
- 각 인덱스의 크기는 32bit이다. 
	- primetive 타입 중 long과 double은 두 칸을 차지하고 나머지는 1칸씩 차지한다
	- byte, char, short Stack Frame 에는  int형으로 변환하여 저장되고, Heap Area에서는 원래형으로 복원된다.
    - boolean도 Stack Frame에서 int 형으로 변환되지만, Heap Area에서 다시 원래 타입으로 복원되지 않는다.
    - long과 double은 64bit의 크기를 가지며 Local Variable Section에서 두 칸을 차지하는데,
두 자료형에 접근할 때는 첫번째 칸의 인덱스로 접근한다. 
	- reference 타입은 주소값이 할당되며  1칸을 차지한다.

Operand Stack

:피연산자 스택, JVM이 Local Variable Section에서 데이터를 가져와 계산할 때 사용하는 작업공간입니다.

👉 특징

  • Stack 자료구조를 갖는다.
  • Local Variable Section에서 값을 받아오거나, 연산한 결과를 돌려주는 역할을 한다.

👉 연산 방식

  • Instruction을 실행하기 위해서 [[Local Variable Section]]에서 값을 복사하여 Operand Stack에 push한다.
  • push된 값을 모두 pop하여 연산을 한다.
  • 연산 후 Operand Stack에 push한다.
  • push된 연산결과를 pop하여 [[Local Variable Section]]에 넣는다.

Frame Data

: Stack Frame의 정보를 저장하는 영역입니다.

👉 구성

  • Constant Pool Resolution 정보(상수풀에 대한 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경)
  • Method 정상 종료 정보(자신 스택프레임을 호출한 상위 스택프레임이 어딘지)
  • Method 비정상 종료 시 Exception 관련 정보(예외 발생 시 처리해야할 정보)

👉 특징

  • Constant Pool Resolution을 통한 Contant Pool 접근
    - Stack Frame은 method가 실행되면서 생성됩니다.
    - 현재 실행 중인 method의 정보를 확인할 수 있고, 해당 method가 속한 class의 상수풀을 참조할 수 있습니다.
    - Constant Pool Resolution은 현재 클래스의 Contant Pool에 접근할 수 있는 포인터 정보입니다.
  • 자신을 호출한 Stack Frame의 Instruction Pointer를 가지고있습니다.
    - Method가 종료되면 JVM은 이 정보를 PC Register에 설정하고 Stack Frame을 빠져나옵다.
    - 만약 return 값이 있으면 자신을 소출한 method의 Stack Frame의 Operand Stack에 push합니다.
  • Method의 비정상 종료에 대한 정보를 담고 있습니다.
    - Exception Table의 Reference를 가지고 있습니다.
    - 각 class 파일은 Exception Table을 가집니다.
    - Exception이 발생하면 jvm은 Exception Table 참조하여 catch절에 해당하는 bytecode로 점프합니다.

Exception Table

fromtotargettype
5912class java/lang/NullPointException
  • from은 try 절이 시작되는 bytecode의 라인 넘버를 의미합니다
  • to는 try 절이 끝나는 bytecode의 라인 넘버를 의미합니다
  • target은 try절에서 Exception이 발생 했을 때 점프해야할 bytecode의 라인 넘버를 의미합니다
  • type은 catch에서 정의한 exception을 의미합니다
  • exception이 발생하면 type의 클래스 정보와 비교하여 target으로 점프할지 여부를 정함.
    비일치 시 Current Frame을 종료 후 이를 호출한 Method의 Stack Frame에 Exception을 다시 던져 위 과정을 반복합니다다.

4. Native Method Stack

🔎 정의

  • Native Code로 되어있는 Function을 호출할 때 생성되는 메모리 영역입니다
  • JVM은 Native Method를 Java Native Interface라는 표준 규약을 통해 제공됩니다.

🔎 특징

  • JVM Stack과 동일하게 Stack 자료구조입니다.
  • Native 언어에 맞게 생성됨. C로 작성되면 C Stack, C++로 작성되어있으면 C++ Stack이 생성됩니다.

🔎 JVM Stack에서 Native Function을 호출

  1. Java metod 1를 호출하면 StackFrame 1 push.
  2. StackFrame 1에서 Native Function을 호출하면 StackFrame 2가 push됨
  3. 그리고 Native Method Stack에도 Stack Frame이 push됨
  4. Native Method Stack의 Stack Frame이 작업을 수행함
  5. Native Function의 작업이 끝나면 Thread는 JVM Stack에 StackFrame 3 을 새로 생성하고 이곳으로 돌아옴. (Native Function을 호출했던 StackFrame 2로 돌아가지 않음)

5. PC Register

🔎 사전 지식

  • CPU 는 Regiser - Base로 명령어(Instruction)을 처리합니다
  • 데이터의 최소단위는 피연산자(Operand), 연산자(Operator)입니다.
  • 이 둘을 합친 것이 명령어(Instruction)입니다.
  • Instruction을 처리할 때 Operand를 저장할 공간이 필요합니다. 이것이 Register입니다.
  • 하지만 Java는 Stack - Base로 명령어를 처리합니다.
  • Java는 플랫폼 독립적이므로 CPU와 그안의 Register를 직접 사용하지 않습니다.

🔎 정의

  • 현재 실행중인 Thread의 Instruction 주소를 저장하는 공간입니다.

🔎 특징

  • Thread가 생성될 때 같이 생성 됩니다.
  • 각 Thread마다 하나씩 생성 됩니다.
  • Thread가 Java Method를 수행하면 PC Register는 이 수행 중인 JVM Instruction의 주소를 가지고있습니다.
  • 만약 Native Method를 수행하면 PC Register는 undefined 상태가 됩니다.
  • 왜냐하면 Native Method는 플랫폼 종속적이므로 JVM을 거치고 실행되지 않습니다.

🔎 필요이유

  • 자바는 멀티 스레드 언어 입니다.
  • Context Switching에 필요한 정보를 담습니다.
  • Thread가 번갈아가며 실행 될 때 해당 Thread의 실행 정보를 담고 있어야 다시 돌아올와 다시 명령어를 수행 할 수 있습니다.

Runtime Data Areas Simulation

Java Variable Arrangement

Simulation

참조:
Java Performance Fundamental - 김한도 저

0개의 댓글