티스토리 뷰
📖 JVM 구조
JVM이란?
JVM은 Java Virtual Machine의 약자로, 자바 가상 머신이라고 부른다. 자바와 운영체제 사이에서 중개자 역할을 수행하며, 자바가 운영체제에 구애 받지 않고 프로그램을 실행할 수 있도록 도와준다. 또한, 가비지 컬렉터를 사용한 메모리 관리도 자동으로 수행하며, 다른 하드웨어와 다르게 레지스터 기반이 아닌 스택 기반으로 동작한다.
먼저, 자바 컴파일러에 의해 자바 소스 파일은 바이트 코드로 변환된다. 그리고 이러한 바이트 코드를 JVM에서 읽어들인, 어떤 운영체제에서든 프로그램을 실행할 수 있도록 만든다.
만먁 자바 소스 파일은 리눅스에서 만들었고 윈도우에서 이 파일을 실행하고 싶다면, 윈도우용 JVM만 설치하면 된다. 이를 통해 JVM은 운영체제에 종속적이라는 특징을 알 수 있다.
📖 JVM 메모리 구조
JVM의 구조는 크게 보면 Garbage Collector, Execution Engine, Class Loader, Runtime Data Area로 나눌 수 있다.
자바 소스 파일은 자바 컴파일러에 의해 바이트 코드 형태인 클래스 파일이 된다. 그리고 이 클래스 파일은 클래스 로더가 읽어들이면서 JVM이 수행된다.
Class Loader
JVM에서 클래스 파일(.class)을 런타임 중에 메모리로 불러오는 역할을 하는 구성 요소이다. 프로그램 실행에 필요한 클래스를 동적으로 로드하고, 이를 JVM이 사용할 수 있도록 준비시킨다.
📖 JVM 메모리 구조
- Class Loader
- Execution Engine
- Garbage Collector
- Runtime Data Area
Execution Engine
클래스 로더를 통해 메모리에 배치된 바이트 코드를 명령어 단위로 읽어 실행하는 역할을 한다. 초기에는 인터프리터 방식으로 코드를 한 줄씩 해석해 실행했기 때문에 속도가 느렸지만, 이후 JIT(Just-In-Time) 컴파일러가 도입되면서 반복 실행되는 코드를 네이티브 코드로 변환하여 성능을 개선하였다. 현재는 인터프리터 방식과 JIT 컴파일 방식을 혼합하여 효율적으로 코드 실행을 수행한다.
Runtime Data Area
JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다. 이 영역은 크게 Method, Heap, Stack, PC Register, Native Method Stack로 나눌 수 있다.
📖 Garbage Collection
가비지 컬렉션은 자바의 메모리 관리 기법이다. JVM의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체를 모아 주기적으로 제거하는 프로세스를 말한다.
C와 같은 프로그래밍 언어는 메모리 할당 및 해제를 수동으로 하지만, Java에서는 이 가비지 컬렉션이 자동으로 처리한다.
장점
개발자가 직접 메모리를 할당하고 해제할 필요가 없어 코드가 단순해진다.
사용하지 않는 객체는 자동으로 수거되므로, 실수로 인한 메모리 누수를 줄일 수 있다.
단점
GC는 JVM이 판단하여 자동 실행되므로, 정확한 동작 시점을 제어할 수 없다.
GC가 수행될 때 애플리케이션 실행이 일시적으로 멈추는 “Stop-The-World” 현상이 발생할 수 있다.
💡 GC를 모니터링해야 하는 이유
GC는 자바에서 메모리를 자동으로 관리하지만, GC가 언제, 얼마나 자주, 얼마나 오래 실행되는지에 따라 애플리케이션의 성능과 응답 속도에 큰 영향을 미칠 수 있다. 특히 GC 실행 중에는 애플리케이션이 일시 정지(Stop-The-World)되기 때문에, 지나치게 잦거나 긴 GC는 성능 저하, 응답 지연, 심할 경우 OutOfMemoryError 등 시스템 장애로 이어질 수 있다. 따라서 GC 로그를 분석하고 메모리 사용 패턴을 모니터링함으로써 적절한 힙 크기 조정, GC 튜닝, 병목 원인 파악 등을 통해 시스템의 안정성과 효율성을 유지할 수 있다.
📖 GC에서 사용하는 알고리즘
✔️ Mark and Sweep
사용 중인 객체를 표시(Mark)하고, 사용되지 않는 객체를 한 번에 제거(Sweep) 하는 방식
✔️ Stop and Copy
힙 메모리를 두 영역으로 나누고, 사용 중인 객체만 다른 영역으로 복사
✔️ Generational GC
객체의 생존 기간에 다라 Young/Old 세대로 나눠 다르게 관리
✔️ Mark and Compact
사용 중인 객체를 마크한 뒤, 한쪽으로 압축해서 메모리 단편화 해결
📖 Java에서 사용하는 GC 알고리즘
✔️ Serial GC
JVM에서 가장 기본적인 형태의 가비지 컬렉터로, 모든 GC 작업을 단일 스레드로 수행한다. GC가 동작하는 동안 모든 애플리케이션 스레드를 정지시키는 Stop-The-World 방식을 사용하기 때문에, 멀티스레드 환경이나 실시간 응답성이 중요한 시스템에서는 부적합하다.
대신 GC 처리 시간이 짧고 구조가 단순해 단일 코어를 사용하는 애플리케이션이나 테스트용, 일시 정지에 민감하지 않은 배치성 작업 등에 적합하다.
✔️ Parallel GC
Parallel GC는 Java 8의 기본 가비지 컬렉터로, GC 작업을 병렬 스레드로 수행함으로써 성능을 향상시킨다. Serial GC와 마찬가지로 GC가 수행될 때 애플리케이션의 모든 스레드는 정지되는 Stop-The-World 방식이지만, 여러 개의 GC 스레드를 활용하여 전체 정지 시간을 줄이고 처리량을 극대화하는 데 중점을 둔다. 따라서 일시 정지가 허용되면서도 높은 처리량이 중요한 서버형 애플리케이션에 적합한 GC 방식이다.
✔️ CMS GC
힙 메모리가 큰 애플리케이션에서 Stop-the-World 시간을 최소화하기 위해 설계된 가비지 컬렉터로, 애플리케이션 스레드와 GC 스레드가 동시에 동작하여 짧은 일시 정지로 객체를 수집한다. GC 과정은 Initial Mark, Concurrent Mark, Remark, Sweep의 단계로 나뉘며, Compact(압축) 과정이 없기 때문에 메모리 단편화가 발생할 수 있는 단점이 있다. 또한 System.gc()와 같은 명시적 GC 호출은 CMS의 동작을 방해하거나 실패를 유발할 수 있어 주의가 필요하다.
✔️ G1 GC
CMS GC의 단점을 개선하기 위해 도입된 가비지 컬렉터로, 대용량 메모리를 가진 멀티 프로세서 환경에서 효율적으로 동작하며, Java 9부터 기본 GC로 채택되었다. 힙 메모리를 동일한 크기의 작은 영역으로 나누어 관리하고, 전체 힙에 대한 동시 마킹을 통해 가장 많은 여유 공간을 확보할 수 있는 영역부터 우선적으로 수집한다. 이러한 방식은 메모리 단편화를 줄이고, 정지 시간을 예측 가능하게 만들며, 불필요한 전체 스캔 없이 수집 효율과 응답성을 향상시키는 것이 특징이다. G1이라는 이름은 바로 “Garbage가 가장 많은 영역부터 먼저(Garbage-First) 수집”하는 전략에서 비롯되었다.
✔️ Z GC
Java 11에서 처음 도입된 지연 시간이 매우 짧은 GC로, Java 15부터 정식으로 사용 가능하다. 이 GC는 GC 작업을 대부분 애플리케이션과 동시에 처리하므로, Stop-the-World 시간이 10ms를 넘지 않아 응답 속도가 중요한 서비스에 적합하다. G1처럼 힙을 여러 영역으로 나눠 관리하지만, 영역 크기가 고정되지 않고 유동적이라는 차이점이 있다. 덕분에 큰 메모리를 사용할 때도 성능 저하 없이 안정적인 처리가 가능하다.
💡 Compact: GC가 객체를 제거한 후, 힙 메모리에는 빈 공간이 생긴다. 이 상태를 메모리 단편화라고 하는데, 이렇게 되면 충분한 총 메모리가 있어도 연속된 공간이 없어 큰 객체를 넣지 못하는 문제가 발생한다. 이를 해결하기 위해 GC는 객체를 제거한 후, 살아남은 객체들을 한쪽으로 몰아서 빈 공간을 정리하는 작업을 하는데 이를 압축(Compact)이라고 한다.
📖 Young Generation & Old Generation
세대 | 설명 | GC 전략 |
Young Generation | 새로 생성된 객체가 저장되는 영역 | 자주 GC 수행, 공간이 작아 빠르고 비용이 낮음 |
Old Generation | 여러 번의 GC에서 살아남은 객체들이 이동하는 영역 | GC 빈도가 낮음, 대신 한 번 수행시 오래걸림 |
📖 메모리 누수 확인
✔️ JVM 메모리 사용량을 모니터링
- VisualVM, JConsole, Java Mission Control 등을 사용해 Heap 메모리 사용량이 계속 증가하는지 확인
- GC가 실행되었음에도 메모리가 줄지 않고 점점 증가하면 의심
✔️ Heap Dump 분석
- jmap -dump 명령어로 Heap Dump 파일을 생성
- GC Root와 연결된 객체 중 메모리 점유가 비정상적으로 큰 객체를 추적
✔️ 의심 코드 직접 확인
- 컬렉션에 객체를 추가만 하고 제거하지 않는 코드
- 리스너 등록 후 해제하지 않는 경우
- static 필드나 싱글톤 객체에 데이터를 보관하는 경우
'Study > CS 스터디 - Java' 카테고리의 다른 글
동시성 (0) | 2025.04.03 |
---|---|
Thread (0) | 2025.03.27 |
컬렉션 (0) | 2025.03.27 |
람다, 스트림, 어노테이션 (0) | 2025.03.20 |
문자열, 예외, 제네릭 (0) | 2025.03.19 |