Java 메모리 모델

ugly duckling·2022년 5월 12일
0

Java

목록 보기
2/4
post-thumbnail

지난번에 JVM의 전체적인 구조와 클래스 로더를 살펴봤다.
Java와 JVM
크게 3가지(클래스 로더, 런타임 데이터 영역, 실행엔진)로 구성된 JVM의 구조에서
오늘은 런타임 데이터 영역을 집중해서 살펴보고자 한다.

이걸 왜 알아야 하는거냐???

Java 메모리 모델을 이해하는 것은 Java application의 개발, 배포, 모니터링, 튜닝에 있어 중요하다. 메모리 모델을 알지 못한다면 다음의 의문들에 대해 다가설 수 없을 것이다.

1. 128GB의 메모리가 장착되어 있고 32bit 운영체제를 사용하는 서버에서 Heap size를 어떻게 설정할 것인가?
2. 같은 메모리에 64bit 운영체제를 사용하는 서버에서는 Heap size를 어떻게 설정할 것인가?
3. 또 Heap Size를 키우면 무엇이 좋은가? 마냥 좋기만 한가?
4. full GC는 무엇이며 어떤 영향을 미치는가? 또한 어떤 종류의 GC를 사용할 것인가?
5. spark, elasticsearch 등 여러 자바 기반의 애플리케이션들의 특성에 맞는 최적화 시 무엇을 고려해야 하나?
6. OutOfMemoryError가 발생했는데 원인은 어찌 알것이며, 어떻게 장애 원인을 해결할 것인가?

Java 메모리 모델만 안다고 해서 위의 의문을 해결할 순 없을 것이다. 그러나 해결을 향한 첫 관문으로 이걸 모르면 나아갈 수가 없다.

Heap, Non Heap

출처 : https://help.sap.com
다음은 Java 8 이전의 메모리 모델을 나타내는 그림이다.

그리고 아래의 그림은 Java 8 버전 이상의 메모리 모델을 나타내는 그림이다.

차이점은 Permanent가 Metaspace로 변경되었다. Permanent와 Metaspace 사이에는 몇 가지 차이점이 있다.

  • Metaspace는 OS에서 제공하는 기본 메모리의 일부로 Heap 외부에 위치하는 반면 Permanent는 Heap 내부에 존재하게 된다.
  • Metaspace는 런타임에 동적으로 크기가 조정된다. 반면 Permanent는 최대 크기가 고정되어있다.
  • Metaspace는 class metadata가 최대 크기에 도달하면 자동으로 GC를 통해 메모리가 관리된다. 반면에 Permanent의 경우 고정 크기를 넘기면 "java.lang.OutOfMemoryError: PermGen"(일명 OOME)를 발생시기 때문에 비용이 많이 드는 빈번한 GC작업을 수행해야 한다.

1. Heap

heap은 위의 그림에서 Java Heap으로 표시된 부분이다. Old Generation과 Young Gerneration 2개의 영역으로 나누어진다. 힙은 JVM이 시작될 때 할당되며 application이 running하는 동안 heap size는 유동적으로 증가하거나 감소한다.

Heap영역이 Old, Young Generation 영역으로 나누어져 있는 이유는 Heap영역의 메모리를 잘 관리하기 위해서다. 그리고 이 Heap의 관리는 GC가 하게 되는데, GC의 2가지 전제조건은 다음과 같다.

1. 대부분의 객체는 금방 unreachable 상태가 된다.
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

저번 포스팅에서 런타임 데이터 영역의 5가지 영역중, Heap 영역을 "공유 자원으로서 JVM당 1개의 힙 영역을 가진다. 인스턴스 또는 배열을 저장하는 공간으로 가비지 컬렉션 대상이다."라고 설명했다. 바로 런타임에 객체나 배열을 동적으로 할당하고 GC를 통해 참조되지 않는 객체를 수거하기 때문에 애플리케이션이 러닝하는 동안 힙사이즈가 변하게 되는 것이다.

1.1 Young Generation

비교적 최근에 할당된 객체들을 가지고 있는 영역이다. Young Generation역역은 다시 3가지 영역으로 나뉜다.
1. Eden memory 영역
2. Survivor Memory0 영역
3. Survivor Memory1 영역
새로 생성된 객체는 Eden 영역으로 할당된다. 이 Eden 영역이 가득 차게되면 minor GC가 수행되며, minor GC로 부터 살아남은 객체는 Suvivor 영역으로 이동된다. 당연히 minor GC에서 살아남은 객체들은 참조되고 있는 객체로 mark과정을 거치게 된다. 이러한 minor GC가 여러번 수행되었을 때도 살아남은 객체들은 오랫동안 사용될 객체로 판단하여 Old generation 영역으로 옮겨지게 된다.

1.2 Old Generation

Old Generation은 minor GC의 여러 사이클에서도 살아남은 객체들이 할당되는 영역으로 오랫동안 사용될 객체들이 할당되는 구역이다. Old Generation이 가득 차게 되면 major GC가 작동하게 된다.

2. Non Heap

Non Heap은 Heap을 제외한 여러 영역을 의미한다. 다음과 같은 영역들이 포함된다.

  • Metaspace
  • Stack Memory 혹은 (Thread Memory)
  • Code Cache
  • Garbage Collector Data

2.1 Metaspace

Non heap 메모리의 상당 부분을 차지하는 영역으로 클래스와 메소드의 metadata를 저장하는 영역이다.
앞서 클래스 로더를 살펴 보았을 때, 클래스 로더가 Class파일을 로딩한 뒤 link와 초기화를 수행하여 Runtime Data Area에 적재한다는 내용이 있었다. 그렇다. 바로 여기에 클래스 로더가 로딩한 메타데이터가 할당되는 것이다. Metaspace는 다음과 같이 2가지 영역으로 구분된다.

  • class space
  • non class space

2.1.1 class space
클래스 관련 메타데이터가 저장되는 곳이다.

  • klass
    java class의 기본정보, 바이트코드를 저장하는 구조체로 클래스 내부 정보를 갖는다.

  • Vtable
    클래스에 있는 각 virtual method의 메타데이터를 보관하는 테이블이다.

  • Itable
    implements로 구현된 interface에 정의된 method의 메타데이터를 보관하는 테이블 이다.

  • Oopmap
    Stack memory에서 객체 참조가 있는 위치를 저장하는 것으로 GC 루트를 찾기위해 사용된다.

2.1.2 Non class space
클래스 관련 메타데이터 이외에 나머지 메타데이터가 저장되는 영역이다.

  • the constant pool
    runtime constant pool이다. JVM은 런타임 상수 풀을 통해 메서드나 필드의 실제 메모리상 주소를 찾아서 참조하는데 이때 필요한 상수를 저장하는 영역이다..

  • method metadata
    메소드의 바이트 코드, 예외 테이블 등을 가지는 영역이다.

  • method counters
    jit 컴파일러 최적화에 사용된다.

  • anotaion metadata

  • 기타 등등

Metaspace의 설명을 보면 런타임 데이터 영역의 메소드 영역이 Metaspace라는 것을 알 수 있다. 런타임에 동적으로 클래스를 로딩하고 로딩된 정보를 담기 때문에 가변적인 길이를 담고 있으며 GC를 통해 수거된다.

2.2 Stack memory (Thread Memory)

각 스레드 마다 가지게 되는 메모리로 ThreadSafe하다. 종료되지 않은 쓰레드가 가진 모든 메소드와 그 메소드 내부의 지역 변수들을 가지고 있게 된다. 이 때문에 멀티쓰레드 환경에서 stack memory에 대한 컨텍스트 스위치가 발생하게된다.(물론 멀티프로세스보다 컨텍스트 스위치 비용이 저렴하다.)

지역 변수로 기본 자료형 이외의 인스턴스는 참조만 저장되고 실제값은 heap영역에 저장되어 있다.
stack memory는 이전 포스팅의 스택 영역이 해당되시겠다.

2.3 Code Cache

jit에 의해 컴파일된 기계코드가 저장되는 곳이다. jit은 이전 포스팅에서 설명했으니 패스...

2.4 Garbage Collector Data

가비지 콜렉터가 작동하려면 당연하게도 가비지 콜렉터를 위한 데이터를 메모리에 할당해야 할 것이다. 힙에 참조되지 않는 객체를 수집하는 작업을 하기 때문에 당연히 가비지 콜렉터 자체의 데이터를 힙에 저장하지 않을 것이다. 따라서 Non Heap에 가비지 컬렉터 자체 데이터를 보관하게 된다.

저번 포스팅에서 JVM 구조와 class loader를 살펴보았고 이번에는 runtime data area영역을 살펴 보았다. 앞으로의 포스팅 계획은
1 . Garbage Collection과 그 종류, Full GC, STW에 대해서 다루고 Heap Size와 이들의 관계에 대해서 살펴보고자 한다.
2. elasticsearch의 JVM heap size 설정 및 디폴드 GC인 CMS GC와 적용가능한 GC의 종류, 장단점에 대해서 살펴보고자 한다.

0개의 댓글