최근 고객사에서 outOfMemoryError가 발생하였다. JVM의 GC 방법과 JVM의 Heap Area에 대해 알아야 해당 문제의 발생 원인과 해결책을 마련할 수 있다.
참조 : 인프런 - 면접 전에 알고 가면 좋을 것들 - 신입 Java 백엔드 개발자편
Runtime Data Area란, JVM이 프로그램을 수행하기 위해 OS로부터 할당 받은 메모리 영역이다. 여기서 중요한 점은 (Total Size) 이다. 톰캣이나 jar를 실행할 때 옵션(파라미터)으로 Total size를 지정해준다. 결국은 유한한 자원을 가지고 있다는 뜻이다.
Runtime Data Area의 크기를 초과하면 메모리 할당 오류가 발생하고, 이는 프로그램이 예상치 못한 방식으로 종료될 수 있음을 의미한다. 이를 방지하려면 메모리 용량을 적절하게 관리하고, 애플리케이션에서 불필요한 객체를 정리하는 등 메모리 최적화가 필요하다.
새로 생성한 객체가 사용되는 영역
객체를 생성하면 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가 자주 일어나는 코드는 좋은 코드가 아니다.
알아야 하는 내용
객체를 생성하면 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가 자주 일어나는 코드는 좋은 코드가 아니다.
JVM에서 운영체제의 메모리를 직접 사용하는 영역으로, Java 애플리케이션의 동작과 관련된 다양한 네이티브 작업이 수행된다. JVM은 Java 객체를 관리하는 Heap과는 별도로, 네이티브 메모리 영역을 활용하여 JVM 자체 및 네이티브 라이브러리의 동작을 지원한다.
운영 중에 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를 선택할 수 있다.