JAVA GC (Garbage Collector)

2021. 4. 11. 03:29 JAVA/Java

1. GC (Garbage Collection)

 

GC란 간략히 정의 하자면 Heap 메모리를 재활용 하기 위해 Root Set에서 참조 되지 않는 Object(Unreachable Object)들을 해제해
가용한 공간을 만드는 작업을 의미 합니다.

Java에서 이런 메모리 해제 작업을 Garbage Collector가 담당하며, 메모리 해제하는 작업을 Garbage Collection이라고 합니다.

 

java8의 은 아래 와 같이 메모리를 관리 합니다. (Java8에서는 기존의 Permanent Generation 이 Metaspace로 대체 됩니다.)

 

Heap 은 Young Generation과 Old Generation 나뉩니다.

Young Generation은 Eden, Survivor space 0,1 로 세분화 됩니다.

 

새롭게 생성된 Object의 대부분은 Young Generation의 Eden 영업에 존재 합니다. Eden 영역이 가득차면 MinorGC가 발생 합니다. Unreachable Object들은 Eden 영역이 클리어 될때 메모리에서 해제 되며, Reachable Object 들은 S0으로 이동 됩니다. 기존에 SO에 있던 Reachable Object 들은 S1으로 이동 됩니다. S1이 가득 차게 되면 S1의 Reachable Object 들은 Old Generation로 복사 됩니다.

대부분의 Old Generation들은 Young Generatione보다 크게 할당되며 크기가 큰 만큼 Young Generation보다 GC가 적게 발생합니다. 이 영역에서 Object들이 사라질때를 MajorGC가 발생한다고 말합니다.

즉, Young Generation은 주기적으로 청소 하고, 상대적으로 오랜 시간 사용되는 object를 Old Generation 에서 관리 하는 방식입니다.

 

이러한 방식의 성능상 이점은 아래와 같습니다.

① Young Generation은 Old Generation 보다 사이즈가 작고, GC가 전체 영역을 처리하는 것보다 시간이 덜 걸립니다.

즉, stop-the-world로 애플리케이션이 중지되는 시간이 짧아집니다.

② Young Generation 영역을 한 번에 모두 비우기 때문에 Young Generation 부분에 해당하는 연속된 여유 공간이 만들어 집니다

(Compaction: 압축, 메모리 공간 정리 작업). 메모리 파편화(memory fragmentation)를 방지할 수 있습니다.

 

•MinorGC : Young Generation 에서 발생하는 GC

•MajorGC : Old Generation (Tenured Space) 에서 발생하는 GC

•FullGC : Heap 전체를 clear 하는 작업 (Young/Old 공간 모두)

 

 

 

2. GC Reachability (Root Set)

 

Java GC는 객체가 가비지인지 판별하기 위해서 reachability라는 개념을 사용합니다.

어떤 객체에 유효한 참조가 있으면 'reachable'로, 없으면 'unreachable'로 구별하고, unreachable 객체를 가비지로 간주해 GC를 수행합니다.

한 객체는 여러 다른 객체를 참조하고, 참조된 다른 객체들도 마찬가지로 또 다른 객체들을 참조할 수 있으므로 객체들은 참조 사슬을 이룹니다.

이런 상황에서 유효한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 합니다.

 

런타임 데이터 영역은 위와 같이 스레드가 차지하는 영역들과, 객체를 생성 및 보관하는 하나의 큰 힙, 클래스 정보가 차지하는 영역인 메서드 영역, 크게 세 부분으로 나눌 수 있습니다. 위 그림에서 객체에 대한 참조는 화살표로 표시되어 있습니다.

 

힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나입니다.

 

• 힙 내의 다른 객체에 의한 참조

 Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조

 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조

 메서드 영역의 정적 변수에 의한 참조

 

이들 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 됩니다.

https://d2.naver.com/helloworld/329631

 

 

 

3. PermGen to Metaspace (JDK 8부터 변경 사항)

 

힙 영역이 기존에 New / Survive / Old / Perm / Native 에서 New / Survive / Old / Metaspace 로 변경 되었습니다.

 

Java7 까지의 Permanent

① Class 의 Meta정보 (pkg path 정보라고 보면 됨, text 정보)

② Method의 Meta 정보

③ Static Object

④ 상수화된 String Object

⑤ Class와 관련된 배열 객체 Meta 정보

⑥ JVM 내부적인 객체들과 최적화컴파일러(JIT)의 최적화 정보

 

Java8 에서의 Metaspace과 Heap 분리

① Class 의 Meta정보 -> Metaspace 영역으로 이동

② Method의 Meta 정보 -> Metaspace 영역으로 이동

 Static Object -> Heap 영역으로 이동

 상수화된 String Obejct -> Heap 영역으로 이동

⑤ 클레스와 관련된 배열 객체 Meta 정보 -> Metaspace 영역으로 이동

⑥ JVM 내부적인 객체들과 최적화컴파일러(JIT)의 최적화 정보 -> Metaspace 영역으로 이동

 

PermGen 영역에 저장되어 문제를 유발하던 Static Obect는 Heap 영역으로 옮겨서 최대한 GC 대상이 되도록 변경 되었습니다.

: Java 개발자라면 OutOfMemoryError: PermGen Space error이 발생했던것을 본적이 있을텐데 이는 Permanent Generation 영역이 꽉 찼을때 발생하고 Memoey leak가 발생했을때 생기게 됩니다. Memory leak의 가장 흔한 이유중에 하나로 메모리에 로딩된 클래스와 클래스 로더가 종료될때 이것들이 가비지 컬렉션이 되지 않을때 발생합니다.

 

수정될 필요가 없는 정보만 Metaspace에 저장, Metaspace는 JVM이 필요에 따라서 리사이징할 수 있는 구조로 개선 었습니다.

PermGen은 Java 8부터 Metaspace로 완벽하게 대체 되었고 Metaspace는 클래스 메타 데이터를 native 메모리에 저장하고 메모리가 부족할 경우 이를 자동으로 늘려 줍니다. Java 8의 장정줌에 하나로 OutOfMemoryError: PermGen Space error는 더이상 볼 수 없고 JVM 옵션으로 사용했던 PermSize 와 MaxPermSize는 더이상 사용할 필요가 없습니다. 이 대신에 MetaspaceSize 및 MaxMetaspaceSize가 새롭게 사용되게 되었다. 이 두 값은 Metaspace의 기본 값을 변경하고 최대값을 제한 할 수 있습니다.

 

 

 

4. 간략한 GC process

 

① 새로 생성된 대부분의 Object들은 Eden 영역에 할당 됩니다. 이때 S0, S1은 비워진 상태로 시작 됩니다.

② Eden 영역이 가득차면, MinorGC 가 발생 합니다.

③ MinorGC가 발생하면, Reachable Object들은 S0으로 옮겨 지며, Unreachable Object들은 Eden영역이 클리어 되는 시점에 메모리에서 해제 됩니다.

④ 다음 MinorGC 가 발생할때, Eden 영역에는 3번과 같은 과정이 발생합니다. 이때 기존에 So에 있던 Reachable Object는 S1으로 움겨 지는데 이때     age 값이 증가되어 옮겨집니다. 모든 Reachable Object들이 S1으로 옮겨 지면, Eden과 S0는 클리어 됩니다. ( Survivor Space 에서 Survivor Space     로의 이동은 이동할때마다 age 값이 증가됩니다 )

⑤ 다음 MinorGC가 발생하면, 4번 과정이 반복 됩니다. S1이 가득차 있으면, S1의 Reachable Object들은 S0로 옮겨 지며 Eden과 S1은 클리어 됩니다.     ( Survivor Space 에서 Survivor Space로의 이동은 이동할때마다 age 값이 증가됩니다 )

⑥ Young Generation에서 계속해서 살아남으며 age값이 증가하는 Object들은 age 값이 특정값 이상이 되면 Old Generation으로 옮겨집니다. 

이러한 단계를 Promotion 이라고 합니다.

⑦ MinorGC 가 계속해서 반복되면, Promotion 작업도 꾸준히 발생하게 됩니다.

⑧ Promotion 작업이 계속해서 반복되면서 Old Generation이 가득차게 되면 MajorGC 가 발생하게 됩니다.

 

 

 

5. GC 방식

 

각 JDK 별 기본 GC

•Java 7 - Parallel GC

•Java 8 - Parallel GC

•Java 9 - G1 (proposed)

 

 

1. Serial Garbage Collector

 

Java 5,6 에서 사용되는 기본 GC 입니다.

MinorGC, MajorGC 모두 순차적으로 시행 됩니다. 모든 GC에서 STW(stop-the-world)이 발생해 서버용으로는 쓰이지 않으며 적은 메모리와 CPU 코어가 적을 때 적합한 방식입니다.

 

Young Generation : generation algorithm

Old Generation : mark-compact-algorithm

 

Option : XX:+UseSerialGC

 

 

2. Parallel Collector(=Throughput Collector)

 

Serial GC 와 동일하나 Young Gen 을 병렬 처리 합니다.

Serial GC 를 기본으로 수행을 하지만, compaction 단계 이전에 summary 라는 단계를 가지며, 해당 작업에서는 이전 GC 이후의 메모리를 인덱싱하는 작업을 우선 수행하고, 해당 인덱스가 마무리된 메모리에 compact 작업을 수행하게 됩니다.
공간에 대한 인덱싱 작업때문에 약간의 메모리를 더 소모할 수 있습니다.

 

멀티스레딩을 할 수 있는 ParallelGC 를 사용하도록 옵션을 주었더라도, 호스트 머신이 싱글 CPU 라면 디폴트 가비지 컬렉터(Serial GC)가 사용됩니다.  

 

가비지 컬렉터 스레드 개수는 디폴트로 CPU 개수만큼이 할당되는데 -XX:ParallelGCThread=<N> 옵션으로 조절이 가능 합니다.

-XX:+UseParallelOldGC 옵션을 사용한다면, old generation 의 가비지 컬렉션에서도 멀티스레딩을 활용할 수 있습니다.

 

Young Generation : generation algorithm (multiple thread)

Old Generation : mark-compact-algorithm

 

Option : -XX:+UseParallelGC 

-XX:ParallelGCThreads=value

 

3. Concurrent Mark Sweep (CMS) Collector

 

application thread 와 garbage collection thread 가 동시에 실행되어 stop-the-world 시간을 최소화하는 GC입니다.

application latency 를 향상 시키는 것에 주안점을 두었습니다.

 

Parallel GC와 CMS GC의 가장 큰 차이점은 Compaction 작업 여부입니다. (Compaction : 메모리 할당 공간 사이에 사용하지 않는 빈 공간이 없도록 옮겨서 메모리 단편화를 제거하는 작업)

Parallel GC 방식에서는 Full GC가 수행될 때마다 Compaction 작업을 진행하기 때문에 시간이 많이 소요됩니다. 하지만, Full GC가 수행된 이후에는 메모리를 연속적으로 지정할 수 있어 메모리를 더 빠르게 할당할 수 있습니다.

CMS GC의 경우 Compaction 작업을 수행하지 않으므로 속도가 빠릅니다. 그러나 메모리 단편화로 인해  Concurrent mode failure이 발생할 수 있고, 이 경우 Compaction 작업을 수행 합니다. CMS GC를 사용할 때에는 Compaction 이 Parallel GC 보다 더 오래 소요되어 문제가 될 수 있습니다. 

CPU리소스가 부족해지거나 메모리 단편화로 인해 메모리 공간이 부족해 지면 Serial GC 방식의 Full GC 발생 합니다.

 

또한 Parallel GC 보다 더 많은 Heap Memory를 사용 하며(약 1~20%), 백그라운드에서 항상 GC Thread가 실행 되어야 해서 CPU 리로스를 많이 소모 합니다.

 

Young Generation :  parallel copy algorithm

Old Generation : concurrent mark-and-sweep algorithm

 

Option : -XX:+UseConcMarkSweepGC 

-XX:+UseParNewGC

-XX:+CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=value

-XX:+UseCMSInitiatingOccupancyOnly

-XX:ParallelCMSThreads=<N> (스레드 개수 설정)

 

4. G1 Garbage Collector

 

Heap 영역이 매우 큰 머신(최소 4GB)에서 돌리기에 적합한 GC입니다. (CMS GC 단점 보완)

 

Heap 영역을 여러개의 Region으로 나누고, 각 Region들은 Young Generation, Old Generation 을 유동적으로 사용 합니다.

 

eden, survivor, old 이외에도 Humongous region과 Available / Unused region 의 2가지 타입의 region이 더 존재 합니다.

Humongous region은 객체의 크기가 큰 경우 사용하는 영역 입니다. 만일 객체가 region 영역의 크기보다 1/2보다 큰 경우 Humongous region을 사용하도록 합니다. (Humongous region에 대한 GC 동작은 최적화 되어 있지 않았습니다.)

Avaialable / Unused region은 아직 사용하지 않은 영역을 의미합니다.

 

Young Generation 을 정리하는 건 Parallel GC나 CMS GC처럼 멀티쓰레드로 정리를 합니다.

 

Old Generation에 해당하는 Region이 여러 개 있을 텐데 CMS처럼 백그라운드 쓰레드로 이 영역들을 정리를 합니다.

Heap 메모리에 객체가 살아 있는지 동시적이고 전역적인 마킹을 수행 합니다. 검사가 끝나면 어떤 영역의 메모리가 가장 많이 비어있는지 확인 할 수 있고 그 공간부터 메모리 회수를 시작 합니다.  그렇게 때문에 Garbage First(G1)이라는 이름을 갖고 있는 것 입니다. 그리고 G1에 의해서 교체를 해야 되는 공간은 배출(evacuation)에 의해서 GC를 시작 합니다.

 

배출은 멀티 프로세스에 의해서 병렬처리가 되기 때문에 Stop-The-World 시간이 줄어들 수 있습니다.

배출은 Region내의 unreachable object 만 삭제 하는것이 아니라 Reachable Object는 다른 Old Generation Region으로 이동시키고 해당 Region은 전체를 클리어 합니다. 다른 Region으로 이동 하는 과정에서 Compacting이 되어 메모리 파편화(Fragmentation)가 생기지 않습니다.

 

장점

① 긴 GC 에 의한 pause time 이 없는 compact 한 빈공간

② GC 의 pause time 예측 가능

③ 처리 성능을 저하시키지 않음 (처리 성능 향상)

④ Java Heap 을 많이 사용하지 않음

 

Full GC

① Initial Mark

② Root Region Scan

③ Concurrent Mark

④ Remark

⑤ Clean Up

⑥ Copy

 

Young Generation : evacution pause

Old Generation : concurrent marking

 

Option : -XX:+UnlockExperimentalVMOptions

-XX:+UseG1GC

-XX:G1HeapRegionSize=size (Region 블럭단위 사이즈 설정 가능, 사용자가 직접 튜닝하는 것은 추천하지 않음)

-XX:MaxGCPauseMills=50 (for a pause time target of 50ms)

-XX:GCPauseintervalMillis=200 (for a pause interval target of 200ms)

 

 

 

참고 링크

https://skyoo2003.github.io/post/2016/10/25/introduction-to-java8

https://d2.naver.com/helloworld/329631

https://yaboong.github.io/java/2018/06/09/java-garbage-collection/
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc

http://initproc.tistory.com/entry/G1-Garbage-Collection

https://sarc.io/index.php/java/255-g1-garbage-collector

 

출처 : code-factory.tistory.com/48?category=724515