본문 바로가기

[Kotlin&Spring] 5기 내일배움캠프

[Kotlin&Spring] 5기 JVM을 메모리 관점에서 보기

오늘은 자바의 구동방식을 이해해보려고 한다

JVM(Java Virtual Machine)은 자바 어플리케이션을 실행하기 위해 설계된 가상머신이다

자바코드를 컴파일하면 바이트코드가 생성되는데, JVM이 이를 실행한다

자바의 Runtime Data Area는 어플리케이션 실행을 위해 JVM이 운영체제로부터 할당받은 메모리 영역이다

아래 설명에서 JVM이 프로그램을 실행하는 방식을 알 수 있다

 

JVM(Java Virtual Machine) 구동방식

1. 자바 컴파일러에 의해 자바 파일(.java)이 바이트코드(.class)로 컴파일 된다

2, 클래스 로더(Class Loader)가 클래스 파일을 Runtime Data Area로 로드한다

3. 정적영역(Static/Method)클래스 파일의 정보와 상위 클래스의 정보 수정자, 변수의 정보, 인터페이스인지 클래스인지 여부를 2진수로 로드한다

4. 로드 후 heap영역에 JVM이 클래스 형식의 객체를 생성한다

스택영역의 메모리는 런타임 스택 실행 후 파괴되며 PC Reigester는 현재 스레드의 실행주소 담는다

5. 다양한 메모리에 존재하는 정보를 사용하여 .class를 실행한다

또한 추가적으로 데이터 타입(자료형)에 따라 각 영역에 나눠서 할당한다

 

Oracle JVM Structure

 

Method 영역은 JVM이 동작하여 클래스가 로딩될 때 생성된다

JVM이 읽어들인 클래스와 인터페이스 대한 메타데이터를 바이트코드로 저장하는 공간이다

Method(Static) 영역에 있는 것은 어느 곳에서나 접근 가능하며 모든 스레드가 공유한다

Method(Static) 영역의 데이터는 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있다

따라서 static 메모리에 있는 데이터들은 프로그램이 종료될 때까지 어디서든 사용이 가능하다

그러나 static 데이터를 무분별하게 많이 사용할 경우 메모리 부족 현상(Memory Leak)이 일어난다

 

메타데이터(Metadata)의 특징은 다음과 같다

1. Type information: 클래스와 인터페이스 정보를 담는다

타입 이름(Symbolic reference): 패키지 이름을 포함한 클래스의 이름을 말한다

타입 종류: 클래스 or 인터페이스 2가지로 이루어진다

타입 제어자: 접근제어자(Access Modifier)와 그 외 abstract, final등의 기타제어자등을 저장한다

연관된 인터페이스 정보: 클래스에 사용된 인터페이스 정보를 저장한다

2. Runtime Constant Pool: Type의 상수 정보를 저장하는 pool이며 인덱스로 접근이 가능하다

클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 Reference를 저장한다

JVM에서는 해당 메소드와 필드의 실제 메모리상의 주소를 찾기 위해 참조한다

Method 영역에 클래스의 정보가 올라오면 Constant Pool에 대한 포인터를 하나 생성하여 언제든지 클래스 내의 상수에 대한 접근이 가능하도록 한다

*Constant Pool Resolution이란,

A 클래스에서 B 클래스를 참조하고 있다면, 클래스 로더는 B 클래스가 메서드 영역에 로딩되어 있는지 확인한다

만약 메서드 영역에 있다면 해당 클래스의 레퍼런스를 B 클래스를 참조하는 변수에 할당한다

하지만, 메서드 영역에 없다면 클래스로더를 통해 B 클래스를 메서드 영역에 로딩한다

그리고 A 클래스의 상수 풀에서는 B 클래스를 참조하는 변수에 B 클래스의 실제 레퍼런스를 할당한다

처음에 A클래스가 참조하고 있는 것은 B 클래스의 클래스 이름(symbolic reference)이다

클래스로더에 의해 B클래스가 로딩되면 실제 B클래스의 참조값(true class's reference)을 가리킨다

3. Field Information: 인스턴스 변수의 정보를 저장한다

타입명: 인스턴스 변수의 타입을 의미한다

타입의 제어자: 접근제어자, 기타제어자등이 속한다

4. Method Information: 메소드의 모든 정보를 저장한다

메소드 이름,메소드 리턴값의 타입, 메소드 매개변수의 수와 각 매개변수의 타입 정보, 필요한 메소드에 대한 정보를 모두 저장한다

5. Class Variable: static 키워드로 선언된 변수를 저장한다

 

Stack Structure

 

Stack 영역에는 메소드 내에서 정의하는 기본 자료형에 해당되는 지역변수의 데이터 값이 저장된다

자료구조의 Stack 과 같이 메소드가 호출될 때 스택 영역에 스택 프레임을 push하고, 데이터를 임시적으로 저장한다 메소드가 종료되면 Stack에서 해당 메소드의 framepop한다

primitive 타입의 데이터에 해당하는 지역변수와 매개변수 데이터 값이 저장된다

메소드가 호출될 떄 메모리에 할당되고 종료되면 메모리에서 사라진다

Stack은 후입선출(LIFO)의 특성을 가지며, scope의 범위를 벗어나면 스택 메모리에서 사라진다

 

스택프레임(stack frame)은 하나의 메서드에 필요한 메모리들(메서드 매개변수, 지역변수, 리턴값 )을 묶은 것을 말한다

하나의 메서드에 하나의 스텍 프레임이 필요하다

메서드 호출하기 직전 스택프레임을 자바 스택이 생성한 후 메서드를 호출한다

스택프레임의 구성요소는 아래와 같다

 

스택 프레임 구성 요소

1. Constant Pool Reference: Method AreaRuntime Constant Pool에 대한 참조(연결고리 역할)

메소드가 문자열리터럴, 심볼릭 참조 또는 상수를 사용하기 위해 Runtime Constant Pool에 대한 참조값을 가져온다

JVM 바이트코드 명령어(1dc, invokeritual )constant pool reference를 통해 런타임 상수 풀의 값을 로드하거나 참조를 해석한다

2. Local Variables Array: 메소드 내의 지역변수를 담고 있는 배열

첫 번째 인덱스에는 현재 인스턴스에 대한 참조값을 저장(this)한다

두 번째 인덱스부터 매개변수 -> 지역변수 순으로 값을 저장한다

3. Operand-Stack : 피연산값과 계산과정에서 생기는 중간값을 저장한다

메소드 내 계산을 위한 작업 공간이다

JVMStack기반으로 연산을 수행한다

연산에 필요한 operand가 모두 Stack에 저장되어있기 때문에 바이트코드는 일반적인 어셈블리어와 달리 operand를 지정하지 않아도 연산이 가능하다

다른 어셈블리어와 달리 연산에 레지스터를 쓰지 않은 이유는 각 디바이스마다 레지스터의 수가 달라 그 수를 가정할 수 없기 때문에 하드웨어의 관여를 최소화하기 위해 JVM에서는 연산과정이 복잡해도 Stack을 사용한다

스레드가 사용할 수 잇는 스택의 크기를 넘을 때 -> StackOverflowError가 발생한다

스택을 동적으로 확장할 떄 확장할 메모리가 부족하거나 새로운 스레드 생성 시 스택에 할당할 메모리가 부족하면 OutOfMemoryError가 발생한다

 

Java 8 이후 메모리 구조 변화

 

Heap 영역은 JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다

new 키워드를 통해 동적으로 생성된 인스턴스 객체가 저장된다

참조형(Reference Type) 데이터 타입을 갖는 객체(인스턴스), 배열 등이 저장되는 공간이다

단, Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 stack에 적재된다

Heap 영역은 Stack 영역과 다르게 보관되는 메모리가 호출이 끝나더라도 삭제되지 않고 유지된다

어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게되면 GC(가비지컬렉터)에 의해 메모리에서 청소된다

Heap 영역이 가득차게 되면 OOM(OutOfMemoryError)이 발생한다

모든 스레드가 공유하는 영역이다

Stack은 스레드 개수마다 각각 생성되지만 Heap은 몇개의 스레드가 존재하든 상관없이 단 하나의 heap 영역만 존재한다

 

Heap 영역은 효율적인 Garbage Collection을 위해 3가지로 구성된다

1. Young(New) Generation

new 키워드를 통해 새로운 인스턴스가 생성되면 Eden 영역에 저장되고, 이후에 Survivor로 이동한다

대부분의 객체가 금방 Unreachable 상태가 되기 때문에 많은 객체가 Young 영역에 생성되었다가 사라진다

시간이 지나면서 데이터는 우선순위(age)에 따라 Old 영역으로 이동하거나 GC에 의해 회수된다(Minor GC)

2. Tenured(Old) Generation

Young Generation 영역에 저장되었던 객체 중 오래된 인스턴스가 이동되어 저장되는 영역이다

접근 불가능 상태로 되지 않아(Reachable 유지) Young 영역에서 살아남은 객체가 여기로 복사된다

대부분 Young 보다 크게 할당하며 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다

Old 영역에 할당된 메모리가 허용치가 넘게 되면, Old 영역에 있는 모든 인스턴스들을 검사하여 참조되지 않는 인스턴스를 한꺼번에 삭제한다(Major GC)

Major GC가 발생하면 GC를 실행하는 스레드를 제외한 모든 스레드가 작업을 중지한다(stop-the-world)

처리 절차는 GC 방식에 따라 달라진다

3. Permanent Generation(Metaspace)

클래스로더에 의해 동적으로 로딩된 클래스의 메타데이터가 저장된다

Java8 이후 Metaspace로 대체되어 Heap영역에서 삭제되고 Native Method Stack에 편입되었다

 

PC Register는 현재 스레드가 실행되는 JVM의 명령어 주소를 저장한다

스레드가 생성될 때 각 스레드마다 할당되는 영역이다

JVMStack-Base 방식으로 작동하기 때문에 Stack에서 Operand를 뽑아내 PC Register에 저장한 다음 연산을 진행한다

멀티 스레드 프로그래밍 환경에서 한 스레드가 작업을 하다가 다른 스레드로 CPU 자원을 넘겨주고 다시 받았을 때, 이어서 작업을 하기 위해 현재 실행중인 명령어(Instruction)의 주소를 기록한다

만약 스레드가 JVM 명령어를 수행중이라면 명령어 주소를 가지고 있지만 Native Method를 수행중이라면 PC RegisterUndefined 상태로 남아있고, 이에 대한 처리는 Native Method Stack에서 담당한다

 

Native Method Stack는 Native 언어(C/C++, 어셈블리)로 작성된 코드들 실행하기 위한 영역이다

자바 이외의 언어가 JVM에서 동작하기 위해 할당한 메모리 영역으로, 일반적으로 C스택을 사용한다

스레드에서 Java메소드가 아닌 Native 방식을 사용하는 메소드를 실행하면 이 곳에 해당 메소드에 대한 정보를 저장한다

JNI(Java Native Interface)를 통해 표준에 가까운 방식으로 구현이 가능하다

 

힙 영역과 스택 영역을 간단히 비교해보았다

Stack 영역

Heap 영역에 생성된 Object 타입의 데이터 참조값이 할당된다

원시타입의 데이터가 값과 함께 할당된다

원시타입의 데이터는 Heap 메모리에 할당되지 않는다

지역변수들은 scope에 따른 가시성(visibility)를 가진다

각 스레드는 자신만의 stack을 가진다

 

Heap 영역

주로 긴 생명주기를 가지는 데이터들이 저장된다

모든 Object 타입의 참조값은 데이터 타입과 함께 Heap 영역에 생성된다

자바의 Wrapper 클래스들과 String 클래스이 Heap 영역에 속한다

:모두 Immutable(변경할 수 없다)하다

heap에 있는 같은 오브젝트를 참조해도 새로운 연산이 적용되는 순간 새로운 객체가 heap에 재할당된다

String 객체에 + 연산이 되어도 글자가 추가되지 않고 새로운 객체가 생성된다

새로운 객체가 heap에 할당되더라도 stack에서 이를 참조하지 않으면 가비지 컬렉션의 대상이 된다

heap 영역에 있는 객체들을 가리키는 참조변수는 stack에 올라간다

 

JVM 구동 방식과 메모리 운용방식을 알게 되면서 자바가 효율적인 프로그램 실행을 위해  클래스, 변수과 값을 메모리에 어떻게 저장하고 실행하는지 알 수 있었다

자바를 잘 이해하게 된 만큼 Spring 공부에서도 이해를 바탕으로 더 좋은 코드를 짤 수 있을 거라고 믿는다

차근차근 내 할 일을 하자 ~ 화이팅 ~