개요
가비지 컬렉션(Garbage Collection)은 프로그래밍에서 메모리 관리를 위한 개념입니다.
이는 JVM에서 사용되는 JRE 중에 하나입니다.
프로그램 실행 과정에서 사용하는 메모리 영역 중에서
더 이상 사용되지 않는 메모리를 자동으로 찾아내고 해제하는 프로세스를 의미합니다.
C언어를 사용하면 malloc() 으로 메모리를 할당하고 free() 함수를 통해 해제하지만
Java나 Kotlin의 경우 개발자가 메모리를 직접 해제해주는 일이 없습니다.
그 이유는 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주기 때문입니다. 이를 GC(Garbage Collection)라고 부릅니다.
Person person = new Person();
person.setName("Me");
person = null;
// 가비지 발생
위처럼 기존 객체가 더이상 참조되지 않고 사용되지 않기때문에 "Me"라고 저장된 Heap 메모리는 메모리 누수 방지를 위해 가비지 컬렉터에 의해 검사되어 청소됩니다.
Stop-the-world라는 키워드가 기존의 프로세스를 멈추고 가비지 컬렉터에 의해 메모리가 비워지는 것을 의미합니다.
그리고 GC-튜닝은 이러한 비워지는 시간을 최소화하는 것을 의미합니다.
즉, GC는 힙 메모리에 저장된 정보를 넣기 위해 비워주는 작업입니다. Perm(Metaspace)에 저장된 것들에 대한 것들을 제외한 나머지를 다시 활용하기 위해 주기적인 관리 업무라고 할 수 있습니다.
문제점
가비지 컬렉터를 사용하지 않으면 생기는 문제점들
- 필요 없는 메모리를 비우지 않았을 때: 메모리 사용을 마쳤을 때 비우지 않을 경우 메모리 누수가 발생할 수 있고 장기적인 관점에서 심각한 문제가 발생할 수 있다.
- 사용중인 메모리 비우기: 존재하지 않는 메모리에 접근하려고 하면 프로그램이 중단되거나 메모리 데이터 값이 손상될 수 있다.
JVM구조에서 Heap영역은 2가지 전제로 설계되었습니다.
1. 객체는 금방 접근 불가능한 상태가 된다.
2. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
위 규칙에 따라 메모리를 효율적으로 사용하기 위해 저장 방법을 다르게 적용합니다.
Python의 가비지 컬렉터와 같이 원래는 Young Generation, Old Generation, Perm영역 3가지가 존재했지만 Java8부터 Young Generation과 Old Generation만 남아서 설계됩니다.
할당을 받은 객체는 Young Generation에 저장되고 지속적으로 사용되는 객체는 Old Generation으로 복사되어 사용됩니다.
Young 영역
Young 영역에서 메모리가 저장되는 방법부터 이해해보면
Young 영역에서 발생하는 가비지 컬렉션은 Minor GC라고 부릅니다. 그리고 Young 영역은 Eden과 Survivor1, 2로 나누어집니다.
객체는 생성되자마자 Eden이란 영역에 저장됩니다.
그리고 MinorGC가 발생할 때 Survivor1과 2로 나뉘어 저장되는데
1을 비울때에는 Survivor1과 Eden영역에 살아있는 객체를 Survivor2에 저장합니다.
하지만 모든 메모리를 Young영역과 같이 사용하게 될 경우 전체 사용 메모리 크기만큼 여유 메모리가 필요하고, 메모리를 옮길때마다
1의 포인터의 값을 저장했다가 Survivor2로 옮겨줘야 합니다.
Minor GC의 시간은 대략 1초내외로 걸립니다.
Old 영역
이를 위해 존재하는 것이 Old영역입니다.
Survivor1과 2를 왕복하며 살아남은 객체는 Old영역에 저장됩니다.
Old영역에서 발생하는 GC는 Major GC라고 부르며 MinorGC의 10배정도 발생합니다. 객체들이 Lived 상태로 있는지 여부를 파악하기 위해 모든 Thread의 실행을 잠시 멈추고 살아있는 객체를 표시하고 사용하지 않는 객체는 제거하여 heap을 정리합니다.
이는 메모리 성능에 악영향을 줄수 있습니다. 이를 위해 JVM 메모리의 구조를 사용자의 의도에 맞게 잘 작성할 필요가 있습니다.
Young Generation은 전체 Heap의 1/2보다 약간 적게 설정하는 것이 좋고, Survivor Space는 Young Generation의 1/8정도의 크기가 적당하다.
java -Xms=256m -Xmx=1536m -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=2 -XXSurvivorRatio=8 MyApp
- -Xms, -Xmx - Heap 사이즈의 최소, 최대값
- -XX:NewSize - Young Generation의 영역의 초기 사이즈
- -XX:MaxNewSize - Young Generation의 최대 사이즈
- -XX:NewRadio - 위와 같을 경우 Old Generation은 Young Generation의 2배의 크기를 갖는다.
- -XX:SurvivorRatio - 위와 같은 경우 Young Generation은 Survivor Space의 8배의 크기를 갖는다.
Java프로그램을 실행하다보면 가비지 컬렉션이 가득차거나 메모리 관리를 잘못할 수 있습니다. 이러한 이유에 대해 이해하고 코드를 만들어야 되는 개발자로서 이러한 개념에 대한 이해를 가지고 있다면 문제를 해결할 수 있고 효율적인 메모리 관리를 할 수 있습니다.
예를 들어 가비지 컬렉션이 가득찬다고 하면, 한번의 스레드에 불필요한 객체 생성과 삭제를 반복할 경우 발생할 수도있고, 가비지 컬렉션 시스템 자체가 충분한 자원을 확보하지 못해 메모리 해제를 진행하지 못하는 경우에 발생할 수 있습니다. 위와 같은 개념을 알고 있다면, 프로그램을 구성하는 코드에서 메모리 누수부분을 찾아 문제를 해결할수 있을것입니다!
Minor GC Vs Major GC Vs Full GC
- Minor GC : Young 부분
- Major GC : Old 부분
- Full GC : Perm부분 - (java 8이후는 metaspace)
Perm, Metaspace 차이 참조!
Perm, Metaspace 차이 참조 내용
Perm 영역
Perm은 Heap에 존재하는 영역으로 상수 static 변수, 런타임 중에 읽은 클래스와 메서드의 메타데이터를 저장하는 장소였습니다. 따라서 PermGen 영역의 크기는 JVM이 사이즈를 지정하고 앱이 실행되면 최대 사이즈가 변경되지 않는 문제가 있었습니다.
동적으로 클래스들이 로드되고 Static 변수, 상수들이 Perm 영역에 쌓이게 되면서 OOM(OutOfMemory)가 자주 발생하는 문제입니다. (Static 변수가 JDK 8이전에는 Perm영역에 저장되었기 때문)
따라서 Perm 영역에서 관리하는 것을 JVM에서 관리하는 것이 아니라 Native Memory로 옮기게 되었고, Static 변수는 Heap영역으로 옮겨져 GC의 대상이 되면서 OOM의 문제를 해결할 수 있게되었습니다.
Metaspace
metaspace는 클래스 로더가 런타임 중에 읽어온 정보가 저장됩니다. Class 구조, Method, Exception 정보, 바이트 코드, Constant Pool, Annotation이 저장됩니다.
또한 JVM에 의해 관리되는 것이 아니라 OS에 의해 관리되는 Native Memory로 소속됩니다.
따라서 OS가 크기를 자동으로 조절하기에 개발자가 영역 확보를 의식할 필요가 없게되었다.
실제로 GC를 튜닝하기 위해 GUI로 확인 하는 방법
VisualVM 설치후 Tool -> plugin에 들어가서 Visual GC를 설치해줍니다.
만약 java.lang.OutOfMemoryError: PermGen space 에러를 본다면 Permanent 영역의 메모리를 -XX:PermGen 과 -XX:MAXPermGEN을 이용해서 늘리면 됩니다.
만약 Full GC가 많아진다면 Old 영역의 메모리 크기를 늘리면 됩니다.
GC의 종류들
애플리케이션에서 사용할 수 있는 GC의 종류는 총 5가지가 있습니다.
- Serial GC(-XX:+UseSerialGC) : Serial GC는 Young(Minor GC)과 Old(Major GC)에 mark-sweep-compact 라는 방법을 이용합니다. 클라이언트의 머신이 독립적인 애플리케이션이거나, CPU 성능이 낮을 때 유용하며 하나의 스레드만 사용합니다.
- Young GC 알고리즘 : Serial
- Old GC 알고리즘 : Serial Mark-Sweep-Compact
- Parallel GC(-XX:+UseParallelGC) : Parallel GC는 시스템에 있는 CPU 코어의 수 만큼 스레드를 만들어 Young(Minor GC)에 이용하는 것을 제외하고는 Serial GC와 같습니다. -XX:ParallelGCThreads=n JVM 옵션을 사용해서 스레드의 수를 조절할 수 있습니다. Parallel GC는 GC 성능을 높이기 위해 여러 CPU를 사용하기 때문에 throughput collector라고도 불립니다. Parallel GC는 Old(Major GC)에서는 한개의 스레드만 사용합니다.
- Young GC 알고리즘 : Parallel Scavenge
- Old Gc 알고리즘 : Serial Mark-Sweep-Compact
- Parallel Old GC(-XX:+UseParallelOldGC) : Young(Minor GC)와 Old(Major GC)에서 모두 여러 개의 스레드를 사용하는 것만 제외 하고는 Parallel GC와 동일합니다.
- Concurrent Mark Sweep(CMS) Collector(-XX:+UseConcMarkSweepGC) : CMS Collector는 Concurrent low pause collector라고도 불립니다. Old(Major GC)에 관련된 GC입니다. CMS Collector는 GC 작업과 애플리케이션이 사용하는 스레드를 동시에 수행하여 Stop-The-World 때문에 일어나는 애플리케이션 중지 상태를 최소화 합니다. Young 영역에서는 Parallel Collector와 똑같은 알고리즘을 사용합니다. 이 GC는 GC때문에 애플리케이션이 정지되는 일이 없어야 할 때 사용합니다. -XX:ParallelCMSThreads=n JVM 옵션을 사용하면 CMS collector의 스레드 수를 조절할 수 있습니다. CMS GC는 Stop-The-World때문에 일어나는 일시 정지 시간을 줄이는 것이 목적이며, STW때문에 일어나는 일시정지를 포함한 평균 응답 시간을 줄이고자 할 때 사용합니다.
- Young GC 알고리즘 : Parallel
- Old GC 알고리즘 : Concurrent Mark-Sweep
- G1 GC(-XX:UseG1GC) : G1 GC는 Java7 에서부터 사용할 수 있으며, CMS collector를 대체하는 것이 주된 목표입니다. G1 GC는 병렬성, 동시성 , 적은 Stop-the-world 를 가진 GC 입니다. G1 GC는 다른 GC와 다르게 Young 영역과 Old 영역이 없습니다. 힙 공간을 여러 개의 동일한 사이즈의 힙 공간으로 분리하는데 분리된 공간을 region이라고 부릅니다. GC가 호출되면 region 중에 liveness가 가장 적은 곳이 GC 됩니다.
- Young GC 알고리즘 : Snapshot-At-The-Beginning(SATB)
- Old GC 알고리즘 : Snapshot-At-The-Beginning(SATB)
참고 : https://www.holaxprogramming.com/2013/07/20/java-jvm-gc/
참고 : https://blog.naver.com/kbh3983/220967456151
참고 : https://jaehoney.tistory.com/177
'기술 스텍 > Java' 카테고리의 다른 글
String과 String Builder, String Buffer 차이점 (1) | 2024.01.11 |
---|---|
고유 락이란? -Java (0) | 2024.01.08 |
[Collection] ArrayList를 왜 List로 받아야 하는가? List의 종류 (0) | 2023.06.24 |
서블릿(survlet) (0) | 2023.06.23 |
java기초(7) 제네릭스 (0) | 2023.01.24 |