JVM Heap Area (Java8)

이재훈·2025년 1월 23일
0

최근 고객사에서 outOfMemoryError가 발생하였다. JVM의 GC 방법과 JVM의 Heap Area에 대해 알아야 해당 문제의 발생 원인과 해결책을 마련할 수 있다.

참조 : 인프런 - 면접 전에 알고 가면 좋을 것들 - 신입 Java 백엔드 개발자편

Runtime Data Area (Total Size)

Runtime Data Area란, JVM이 프로그램을 수행하기 위해 OS로부터 할당 받은 메모리 영역이다. 여기서 중요한 점은 (Total Size) 이다. 톰캣이나 jar를 실행할 때 옵션(파라미터)으로 Total size를 지정해준다. 결국은 유한한 자원을 가지고 있다는 뜻이다.

Runtime Data Area의 크기를 초과하면 메모리 할당 오류가 발생하고, 이는 프로그램이 예상치 못한 방식으로 종료될 수 있음을 의미한다. 이를 방지하려면 메모리 용량을 적절하게 관리하고, 애플리케이션에서 불필요한 객체를 정리하는 등 메모리 최적화가 필요하다.

Heap Space

Young generation

새로 생성한 객체가 사용되는 영역

  • Minor GC 대상 영역
  • Eden, From, To 요소로 구성

Eden

  • 객체 생성 직후 저장되는 영역
  • Minor GC 발생 시 Survivor 영역으로 이동
  • Copy & Scavenge 알고리즘 사용

Survivor 0, 1

  • Minor GC 발생 시 Eden, S0에서 살아남은 객체는 S1으로 이동
  • S1에서 살아남은 객체는 Old영역으로 이동

Old (Old generation)

  • Young generation 영역에서 소멸하지 않고 남은 개체들이 사용하는 영역
  • Full GC 발생 시 개체 회수 → 지연이 됌 → 지연이 길어질 시 장애 발생
  • Mark & Compact 알고리즘 사용

알아야 하는 내용

객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.

survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.

문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.

Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.

알아야 하는 내용

객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.

survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.

문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.

Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.

Metaspace (Java8)

  • 문자열 상수
  • 로드되는 클래스, 메서드 등의 관한 메타 정보를 저장.
  • Java heap이 아닌 Native 메모리 영역을 사용 (속도 문제)
  • Spring 애플리케이션에서 Metaspace를 많이 사용하는 경향이 있음 (속도 문제)
  • 리플랙션 클래스 로드 시 사용 (Spring)
  • 어떤 경우에는 Heap Space 보다 Metaspace 영역의 크기가 커야하는 경우도 있다.
  • TPS가 많으면 Metaspace영역을 많이 사용한다.

알아야 하는 내용

객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.

survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.

문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.

Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.

Metaspace (Java8)

  • 문자열 상수
  • 로드되는 클래스, 메서드 등의 관한 메타 정보를 저장.
  • Java heap이 아닌 Native 메모리 영역을 사용 (속도 문제)
  • Spring 애플리케이션에서 Metaspace를 많이 사용하는 경향이 있음 (속도 문제)
  • 리플랙션 클래스 로드 시 사용 (Spring)
  • 어떤 경우에는 Heap Space 보다 Metaspace 영역의 크기가 커야하는 경우도 있다.
    - TPS가 많으면 Metaspace영역을 많이 사용한다.

Native Area

JVM에서 운영체제의 메모리를 직접 사용하는 영역으로, Java 애플리케이션의 동작과 관련된 다양한 네이티브 작업이 수행된다. JVM은 Java 객체를 관리하는 Heap과는 별도로, 네이티브 메모리 영역을 활용하여 JVM 자체 및 네이티브 라이브러리의 동작을 지원한다.

Heap 메모리와 독립적

  • Native Area는 Java Heap 과는 별도의 영역
  • GC의 영향을 받지 않으며, 직접 관리되어야 한다.

JNI 및 네이티브 코드

  • JNI를 통해 호출된 네이티브 메서드가 메모리를 할당하거나 사용하는 경우 이 영역을 사용한다.

JVM 내부의 네이티브 작업

  • ClassLoader, Thread, JIT 컴파일러, 메서드 호출 스택 등 JVM 내부 동작과 관련된 리소스가 저장된다.

메모리 관리

  • Native Area 메모리는 Java에서 GC의 영향을 받지 않으므로 명시적으로 해제해야 한다.
  • 누수가 발생하면 OutOfMemoryError 또는 시스템 메모리 부족 상태를 유발할 수 있다.

Runtime Data Area 주요 에러

OutofMemoryError: Java heap space

  • Heap 영역에 할당할 수 있는 메모리 공간이 부족할 떄 발생하는 오류. 객체를 더 이상 생성할 수 없으며, JVM이 동적으로 할당하려는 메모리가 부족하다는 신호이다.
  • 해결 방법 : JVM의 메모리 할당 크기를 늘리거나 메모리 누수를 해결해야 한다.
  • 예: -Xmx 옵션으로 힙 메모리 크기를 늘릴 수 있다.

OutOfMemoryError: PermGen space / Metaspace

  • Method Area 또는 metaspace(Java8 이후)에 클래스 메타 데이터나 JIT 컴파일된 코드가 저장된다. 이 공간이 초과되면 해당 오류가 발생한다.
  • 해결 방법 : -XX:MaxMetaspaceSize 옵션으로 메타스페이스의 크기를 설정할 수 있다.

StackOverflowError

  • Stack 영역의 크기를 초과할 때 발생할 수 있는 오류이다. 주로 재귀 호출로 인해 스택 메모리가 초과될 수 있다.
  • 해결 방법 : 재귀 호출을 줄이거나 JVM 스택 크기를 늘릴 수 있다.(-Xss 옵션)

어떻게 해결할까 ?

운영 중에 outofMemoryError를 만난다면 당황할 수 밖에 없다. 결론은 코드에서 찾아야 한다. 코드에서 Young generation에서 오래 살아남은 객체들이 Old generation에 과도하게 많이 올라가는 경우, Metaspace 영역을 많이 사용하는 경우 빈번한 Full GC가 일어나면서 Stop-the-World가 이뤄지고 이것은 장애로 이루어질 수 있다.

하지만 JVM 설정과 GC 종류를 변경하여 해결하는 방법도 있다. 또한 APM (Application Performance Management) 을 사용하여 애플리케이션을 모니터링하고 부하 테스트를 통해 원인을 찾아야 한다.
이런 경우에는 JVM에 다양한 옵션을 통해서 Heap, Stack, Metaspace 영역을 늘리고, 그것에 맞게 GC를 선택할 수 있다.

이런 경우에는 JVM에 다양한 옵션을 통해서 Heap, Stack, Metaspace 영역을 늘리고, 그것에 맞게 GC를 선택할 수 있다.

GC 종류

Serial GC

  • 단일 스레드 환경 및 소규모 응용 프로그램을 위한 간단한 GC
  • Minor GC에서 Copy & Scavenge 알고리즘 적용
  • Full GC에서 Mark & Compact 알고리즘 적용

Parallel GC

  • JVM 기본 옵션(Java 8 기본)
  • 멀티 스레드 기반 (개수 지정 가능)으로 작동해 효율을 높임
  • Low-pause (응용 프로그램 중단 최소화)
  • Throughput (Mark & Compact 알고리즘 기반으로 신속성 최대화)

Concurrnet GC

  • Low-pause와 유사하며 응용 프로그램 실행 중 GC 실행
  • 동작 중지 최소화

Incremental GC (Train GC)

  • Concurrent GC와 유사하나 Minor GC 발생 시 Full GC를 일부 병행
  • 경우에 따라 오히려 더 느려지는 부작용

G1 (Garbage First) GB

  • 4GB 이상 대용량 Heap 메모리를 사용하는 멀티스레드 기반 응용 프로그램에 특화된 GC
  • Heap을 영역(1~32MB)단위로 분할한 후 멀티스레드로 스캔
  • 가비지가 가장 많은 영역부터 수집 실시

    보통 Heap 메모리를 4G 이상으로 변경한 후 G1GB 설정하는 것으로 GC 튜닝 할 수 있다.
profile
부족함을 인정하고 노력하자

0개의 댓글

관련 채용 정보