Java의 JVM 메모리 구조

hjkim·2021년 11월 16일
3

JVM 메모리 구조

우리가 흔히 code editor에서 작성하는 Java 언어는 컴퓨터가 이해할 수 없는 형태이다. 따라서 이를 컴퓨터가 이해할 수 있는 기계어로 변환을 시켜줘야 한다. 이때, 컴퓨터가 이해할 수 있는 기계어의 형식은 각 OS별로 다르므로 JVM이 중간에서 Java 코드를 각 OS가 이해할 수 있는 형식의 기계어로 바꿔 실행하는 데 도움을 준다.

JVM의 구조는 크게 4가지로 나뉜다.

1. Class Loader

Person.java 파일을 작성한 후 javac 명령어를 통해 컴파일하면 Person.class와 같이 .class 파일이 생성된다. 이 .class 파일은 java 코드를 바이트 코드로 변환한 파일이다. Class Loader는 이렇게 생성된 class 파일들을 엮어 JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area에 적재하는 역할을 한다.

2. Execution Engine

Class Loader에 의해 메모리에 적재된 바이트 코드들을 기계어로 변경하여 실행하는 역할을 한다. Interpreter 방식과 JIT(Just-In-Time) 컴파일러를 이용하는 방식이 있다. Interpreter 방식은 명령어 단위로 바이트 코드를 읽어 실행한다. Interpreter 방식이 한 줄씩 수행하기 때문에 느리다는 단점이 있어 이 단점을 보완하기 위해 JIT 컴파일러 방식이 도입되었다. JIT 컴파일러 방식은 자주 실행되는 바이트 코드들에 대해 전체를 컴파일 하여 네이티브 코드로 변경하는 방식이다. 네이티브 코드는 캐시에 보관되기 때문에 한 번 컴파일된 코드는 빠르게 수행이 가능하기 때문이다. 하지만 한 줄씩 컴파일하는 Interpreter 방식에 비해 바이트 코드 전체를 컴파일하는 JIT 방식의 속도가 훨씬 느린 탓에 Interpreter 방식과 JIT 방식을 혼용하는 것이 가장 효율적이다.

3. Garbage Collector

Heap 메모리 영역에 생성된 객체들 중 참조되지 않는 객체들을 탐색 후 제거하는 역할을 한다. GC가 수행되는 동안은 GC를 수행하는 쓰레드 외의 모든 쓰레드는 일시정지된다. 특히 Full GC가 발생하면 수 초간 모든 쓰레드가 정지하게 되어 장애로 이어지는 문제가 발생할 수 있다.

4. Runtime Data Area

JVM의 메모리 영역으로 자바를 실행할 때 사용되는 데이터들을 적재하는 역할을 한다.

4-1. PC Register

Thread가 시작될 때마다 생성되며 Thread마다 하나씩 존재한다. 현재 Thread가 실행되는 부분의 주소와 명령을 저장하고 있어 어떤 부분을 무슨 명령으로 실행해야할 지에 대한 기록을 한다.

4-2. JVM Stack

각종 형태의 변수나 임시 데이터, 스레드나 메소드의 정보를 저장한다. 할당했다가 바로 소멸시켜야 하는 특성을 가진 데이터를 저장하기 위한 영역이다. 메소드 호출 시마다 각각의 스택 프레임(메서드를 위한 공간)이 생성되고, 수행이 끝나면 프레임 단위로 삭제된다.

4-3. Native Method Stack

JAVA 언어가 아닌 다른 언어로 작성된 코드를 위한 공간이다. C/C++ 코드를 실행시켜 kernel에 접근할 수 있다.

4-4. Heap

모든 Thread가 공유하며, new 키워드로 생성된 객체와 배열이 생성되는 영역이다. GC가 참조되지 않는 메모리를 확인하고 제거하는 영역에 해당된다. 세 부분으로 나누어진다.

  • New/Young
    객체들이 최초로 생성되는 공간인 Eden과 Eden에서 참조되는 개체들이 저장되는 공간인 Survivor0/1이 해당 영역에 해당된다. Minor GC가 발생한다.
  • Old
    New 영역에서 일정 시간 참조되고 있는, 살아남은 객체들이 저장되는 공간이다. Major GC가 발생한다.
  • Permanent
    생성된 객체들의 Meta 정보가 저장되는 영역이고 JVM에 의해 사용된다. Major GC가 발생한다. Java 7까지만 Heap 영역에 존재하고 Java 8 버전부터는 Metaspace가 이를 대체한다.

5. Method Area

클래스 정보를 처음으로 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다. 클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보와 같은 필드 정보와 메소드 이름, 리턴 타입, 파라미터, 접근 제어자 정보 같은 메소드 정보, interface인지 class인지 나타내는 타입 정보가 저장되어 있다. 내부의 constant pool 에는 문자 상수, 타입, 필드, 객체 참조 등이 저장된다. static 변수, final class 변수 등이 생성되는 영역이다. 이때, 기본형이 아닌 static 클래스형 변수는 참조형만 저장되고 실제 인스턴스는 Heap에 저장되어 있다. 해당 인스턴스의 변수를 저장하기 위해서는 Heap에 메모리가 확보되어야 한다.


GC 동작 메커니즘

GC는 한정된 메모리 공간 내에서 새로운 객체의 할당을 위해 Heap 공간을 재활용하려는 목적을 갖는다.

1. Minor GC

새로 생성된 대부분의 객체는 Heap의 Eden 영역에 위치한다. Eden 영역에 객체가 가득 차게 되면 GC가 한 번 발생한다. 살아남은 객체는 Survivor 영역 중 하나로 이동시키고 Eden 영역에 남은 객체들을 제거한다. 이 과정을 계속 되풀이하는 과정에서 끝까지 살아남아 있는 객체는 일정시간 지속적으로 참조되고 있다는 의미이므로 Old 영역으로 이동한다.

2. Major GC

Minor GC보다 시행 횟수가 적다. Old 영역에 객체가 가득 차게 되면 Old 영역의 모든 객체들을 검사하여 참조되지 않은 객체들을 한꺼번에 삭제한다. 그러면서 Heap 메모리 영역의 중간중간에 빈 메모리 공간이 발생하게 되는데 이 부분을 없애기 위해 재구성을 진행하게 된다. 메모리 재구성이 일어나는 도 중 다른 Thread에서 메모리를 사용해버리면 안되기 때문에 GC를 실행하는 Thread를 제외한 나머지 Thread의 작업이 모두 멈추게 된다. 이것을 'stop-the-world'라고 하며, GC 작업이 끝나야 중단되었던 작업을 재시작할 수 있다.

3. Garbage 판단 기준

GC가 동작하기 전, 특정 객체가 garbage인지 아닌지 판단하기 위해 사용되는 개념은 reachability이다. heap 영역에 할당된 객체에 유효한 참조가 있다면 reachability, 없다면 unreachability로 판단한다. 객체들은 총 4가지 경우에 참조를 갖는다.

  1. 힙 내의 다른 객체에 의한 참조
  2. Java Stack으로부터의 참조
  3. Native Stack에 의해 생성된 객체에 대한 참조
  4. Method Area의 정적 변수에 의한 참조

Garbage Collection은 위와 같은 참조를 하고 있는 Reachable Object를 스캔하는 것을 Mark, Mark 되어있지 않은 모든 Object들을 Heap 영역에서 제거하는 것을 Sweep이라 하여 'Mark-and-Sweep'이라고도 한다.


[참조1] https://jeong-pro.tistory.com/148
[참조2] https://asfirstalways.tistory.com/158
[참조3] https://www.holaxprogramming.com/2013/07/16/java-jvm-runtime-data-area

profile
피드백은 언제나 환영입니다! :)

0개의 댓글