가비지 컬렉션(Garbage Collection) 이해하기
모든 객체는 메모리와 같은 시스템 리소스를 사용합니다. 더 이상 필요하지 않은 경우, 이러한 리소스를 시스템에 반환할 수 있는 체계적인 방법이 필요합니다. 그렇지 않으면 "리소스 누수(resource leaks)"가 발생할 수 있습니다.
JVM과 자동 가비지 컬렉션
JVM(Java Virtual Machine)은 더 이상 사용되지 않는 객체가 차지한 메모리를 회수하기 위해 자동 가비지 컬렉션을 수행합니다. Java 객체는 프로그램에 할당된 메모리 섹션인 힙(Heap)에 생성됩니다. 객체가 더 이상 필요하지 않을 때, 가비지 컬렉터는 이러한 사용되지 않는 객체를 찾아 추적하고 메모리를 확보하기 위해 삭제합니다. 가비지 컬렉션 없이는 힙이 결국 메모리 부족으로 인해 OutOfMemoryError 런타임 오류가 발생할 수 있습니다.
가비지 컬렉터는 프로그램이 종료되기 전까지 언제든지 실행될 수 있으며, 종종 시간이 지난 후에 발생할 수도 있습니다. 따라서 C나 C++과 같은 언어에서 메모리 누수가 발생할 가능성이 높은 반면, Java에서는 자동으로 메모리를 관리하기 때문에 메모리 누수가 덜 발생할 가능성이 있습니다.
메모리 및 자원 누수
메모리 누수 외에도 자원 누수가 발생할 수 있습니다. 예를 들어, 애플리케이션이 디스크에 있는 파일을 열어 내용을 수정한 후 파일을 닫지 않으면, 다른 애플리케이션이 해당 파일을 사용하기 전에 해당 애플리케이션이 종료되어야 합니다. 일반적인 애플리케이션에서는 가비지 컬렉터가 결국 수집 대상이 되는 객체들에 대한 메모리를 회수할 수 있습니다. 그러나 JVM은 가비지 컬렉터가 언제 실행될지, 혹은 실행될지에 대한 보장을 제공하지 않습니다. 가비지 컬렉터가 실행될 때, 모든 객체가 수집되거나 일부만 수집될 수도 있습니다.
메모리 힙 세대(Memory Heap Generations)
자바 가비지 컬렉션의 작동 방식을 완전히 이해하려면 메모리 힙의 다양한 세대에 대해 알아야 합니다. 이러한 세대는 가비지 컬렉션을 더 효율적으로 만드는 데 도움이 됩니다. 힙은 다음과 같은 공간으로 분할됩니다.
1. Eden
- 설명: 객체가 생성되는 메모리 풀입니다.
- 특징: Eden 공간이 가득 차면, 가비지 컬렉터는 사용되지 않는 객체를 제거하거나 아직 사용 중인 경우 Survivor 공간으로 이동시킵니다.
- 세대: Young 세대의 일부입니다.
2. Survivor
- 설명: JVM에는 두 개의 Survivor 공간이 있습니다: Survivor 0과 Survivor 1.
- 특징: Eden에서 살아남은 객체들이 일시적으로 저장되는 공간입니다.
- 세대: Young 세대의 일부입니다.
3. Tenured (Old Generation)
- 설명: 오래된 객체가 저장되는 공간입니다.
- 특징: 객체가 일정 주기 동안 가비지 컬렉션을 견디면 이 공간으로 이동됩니다. Eden 공간보다 훨씬 크며, 가비지 컬렉터가 덜 자주 확인합니다.
- 세대: Old 세대로 간주됩니다.
이러한 다양한 공간은 가비지 컬렉션을 더 효율적으로 만드는 데 기여합니다. 대부분의 가비지 컬렉션은 Eden 공간에서 발생하며, 객체가 Survivor 및 Tenured 공간으로 이동함으로써 가비지 컬렉터는 메모리에 계속 필요한 객체를 덜 자주 확인하게 됩니다. Tenured 공간은 크기가 크기 때문에 덜 자주 채워지며, 가비지 컬렉터가 덜 확인합니다. 단점으로는 Tenured 공간이 덜 자주 확인되기 때문에 메모리 누수에 민감할 수 있습니다.
가비지 컬렉션 주기
- Young 세대 (Eden 및 Survivor): "마이너 가비지 컬렉션"이라고 하며, 더 자주 발생하고 빠릅니다.
- Old 세대 (Tenured): "오래된 가비지 컬렉션" 또는 "메이저 가비지 컬렉션"으로 알려져 있으며, 마이너 가비지 컬렉션보다 오래 걸립니다.
참고: Java 8 이전에는 JVM에 perm gen(permanent generation)이라는 메모리 영역이 있었으나, Java 8부터는 제거되었습니다.
가비지 컬렉터 옵션
Java에서는 네 가지 다른 가비지 컬렉터 옵션을 제공합니다. 각각의 가비지 컬렉터는 고유한 장단점을 가지고 있습니다.
1. Serial 가비지 컬렉터
- 특징: 단일 스레드 환경에서 사용됩니다.
- 장점: 간단한 구현.
- 단점: 프로덕션 환경에서는 사용하지 않는 것이 좋습니다. 가비지 컬렉션 프로세스가 스레드를 점유하여 다른 프로세스를 멈추게 하기 때문입니다("stop-the-world" 이벤트). CPU 코어가 하나일 때 사용하기 위해 설계되었으나, 애플리케이션 성능에 큰 영향을 미칩니다.
2. Parallel 가비지 컬렉터
- 특징: JVM의 기본 가비지 컬렉터입니다. 여러 병렬 스레드를 사용하여 처리량을 높입니다.
- 장점: 다중 CPU를 활용하여 높은 처리량을 제공합니다.
- 단점: 가비지 컬렉션을 실행할 때 애플리케이션 스레드도 멈출 수 있습니다.
3. Concurrent Mark-and-Sweep (CMS) 컬렉터
- 특징: 여러 스레드를 사용하여 병렬로 작동합니다.
- 장점: "Low Latency" 컬렉터로 알려져 있으며, 애플리케이션 스레드를 덜 자주 멈춥니다. 사용자 지향 애플리케이션에 적합합니다.
- 단점: 젊은 세대를 수집할 때에도 실행을 멈추어야 하며, 컬렉터 스레드가 애플리케이션 스레드와 동시에 실행되기 때문에 더 많은 처리량을 사용합니다.
4. Garbage First (G1) 가비지 컬렉터
- 특징: 힙을 여러 영역으로 분할하여 동시에 모두 수집합니다.
- 장점: 큰 영역을 한꺼번에 비우는 대신 작은 영역을 비워 컬렉션 프로세스를 최적화합니다. CMS 컬렉터처럼 거의 실행을 멈추지 않고 young과 old 세대를 모두 수집할 수 있습니다.
- 단점: 복잡한 구현으로 인해 일부 상황에서는 예상치 못한 동작을 할 수 있습니다.
가비지 컬렉션 강제 실행
가비지 컬렉션을 강제로 실행할 수는 없지만, JVM이 힙을 거의 100% 사용하고 있는 경우에 가비지 컬렉션을 유도할 수는 있습니다. Java 객체가 가비지 컬렉션되도록 보장하기 위해 몇 가지 트릭을 사용할 수 있습니다.
참고 자료
https://d2.naver.com/helloworld/1329
https://newrelic.com/blog/best-practices/java-garbage-collection
https://www.itworld.co.kr/news/224419
'ComputerScience > Java' 카테고리의 다른 글
[Java] JUnit5 (0) | 2024.02.22 |
---|---|
[Java] Java에서 Queue와 구현체들 (+ ArrayList와 LinkedList의 차이) (0) | 2024.01.31 |
[Java] List와 Set의 차이, 그러면 Set은 어떻게 구현할까요? (0) | 2024.01.29 |
[Java] 인터페이스가 다중 상속이 가능한 이유? (0) | 2024.01.07 |
[Java] abstract vs interface 언제 써야하는가? (2) | 2023.12.07 |