Concurrent GC

EXEM Knowledge Base

(Concurrent Full GC에서 넘어옴)
Jump to: navigation, 찾기

목차

[편집] 개요

CMS CollectorTenured Generation에 대한 GC 작업을 Applictaion Thread의 Pause를 최소화하면서 Concurrent하게 진행한다. CMS Collector에 의한 GC 작업을 Concurrent GC라고 부른다. SWT(Stop The World) 방식의 Full GC가 가지는 단점을 최소화하고 응답 시간을 최적화하기 위해 고안된 GC 방법이다.

[편집] 동작 방식

[편집] 단계

Concurrent GC는 다음과 같은 단계로 이루어진다.

  1. Initial Mark Phase: Root Object에서 의해 직접 참조되는 Object들을 Marking한다. 이 단계 동안 모든 Application Thread들은 Stop된다.
  2. Concurrent Mark Phase: Initial Mark Phase에서 Mark된 Object들에 의해 참조되는 Object들들 Marking한다. 이 단계는 Concurrent하게 진행된다.
  3. Concurrent Preclean Phase: Concurrent Mark 단계동안 추가/변경된 Object들을 미리 스캔(Scan)해둔다. 다음 단계인 Rescan 단계의 부담을 줄이기 위해서이다. 이 단계는 Concurrent하게 진행된다.
  4. Rescan Phase: 앞 단계에서 Scan되지 않은 잔류 Object들을 모두 Scan한다. 이 단계 동안 모든 Application Thread들은 Stop된다.
  5. Concurrent Sweep Phase: 앞 단계에서 Mark되지 않은 Object들, 즉 Dead Object들을 청소한다. 이 단계는 Concurrent하게 진행된다.
  6. Concurrent Reset Phase: Mark & Sweep 단계가 끝난 후의 정리 작업으로, CMS 작업에 필요한 데이터 구조들을 초기화하는 작업이 이루어진다. 이 단계는 Concurrent하게 진행된다.

전체 6 단계 중 Initial Mark Phase 단계와 Rescan Phase만이 STW 방식으로 동작하고 나머지 단계들은 모두 Concurrent하게 진행된다. 따라서 사용자가 체감하는 GC Pause시간을 최소화할 수 있다.

[편집] 예제

아래에 Concurrent GC에 의한 GC Log의 간단한 예제가 있다.

-- Initial Mark Phase
40.146: [GC [1 CMS-initial-mark: 26386K(786432K)] 26404K(1048384K), 0.0074495 secs]

-- Concurrent Mark Phase
40.154: [CMS-concurrent-mark-start]
40.683: [CMS-concurrent-mark: 0.521/0.529 secs]

-- Concurrent Preclean Phase
40.683: [CMS-concurrent-preclean-start]
40.701: [CMS-concurrent-preclean: 0.017/0.018 secs]

-- Rescan Phase
40.704: [GC40.704: [Rescan (parallel) , 0.1790103 secs]
40.883: [weak refs processing, 0.0100966 secs] 
     [1 CMS-remark: 26386K(786432K)] 52644K(1048384K), 0.1897792 secs]

-- Concurrent Sweep Phase
40.894: [CMS-concurrent-sweep-start]
41.020: [CMS-concurrent-sweep: 0.126/0.126 secs]

-- Concurrent Reset Phase
41.020: [CMS-concurrent-reset-start]
41.147: [CMS-concurrent-reset: 0.127/0.127 secs]

[편집] Troubleshooting

[편집] Permanent Generation과 Full GC

일반적으로 Full GC가 발생하면 Permanent Generation에 대한 Collection 작업도 같이 이루어진다. 하지만 Concurrent GCPermanent Generation에 대한 Collection을 수행하지 않는다. 따라서 Class Loading/Unloading이 빈번한 Application에서는 Permanent Generation이 꽉차는 현상이 발생하게 된다. Permanent Generation이 꽉 차면 JVM은 Concurrent 방식이 아닌 STW 방식의 Full GC를 수행한다. 이 문제를 피하려면 다음 두 개의 옵션을 활성화해주어야 한다.

  • -XX:+CMSPermGenSweepingEnabled
  • -XX:+CMSClassUnloadingEnabled

옵션에 대한 자세한 설명은 JVM Options를 참조한다.

아래에 Permanent Generation에 의한 Full GC가 발생한 상황의 GC Log 예제가 있다. 12M 크기가 메모리 영역이 꽉 차고, 그로 인해 Full GC가 발생하는 것을 확인할 수 있다.

21.641: [Full GC 21.641: [CMS: 12167K->10175K(17064K), 0.3359514 secs] 15184K->10175K(21096K), 
[CMS Perm : 12287K->1376K(12288K)], 0.3364375 secs]

[편집] Tenured Generation Fragmentation과 Full GC

Concurrent GC는 일반적인 Full GC와는 달리 Tenured Generation에 대한 Compaction 작업을 매번 수행하지 않는다. 따라서 메모리 영역이 단편화(Fragmentation)될 확률이 높다. Tenured Generation이 단편화되면, Young Generation에 거주하던 Object들에 대한 Promotion 작업이 실패할 확률이 높다. 가령 1M 크기의 Chunk 100개로 쪼개진 Tenured Generation은 비록 여유 메모리가 100M에 달하지만, 2M 크기의 Object들을 적재하지 못하게 된다. 이런 현상을 흔히 Promotion Failure라고 부른다. Promotion Failure가 발생하면 JVM은 즉각 Full GC를 수행한다. 이 문제를 피하기 위해 다음과 같은 방법을 사용할 수 있다.

  • -XX:CMSFullGCsBeforeCompaction=0 옵션과 -XX:+UseCMSCompactAtFullCollection 옵션을 사용한다. 자세한 내용은 JVM Options를 참조한다.
  • -XX:CMSInitiatingOccupancyFraction=<50 이하의 값> 옵션과 -XX:+UseCMSInitiatingOccupancyOnly 옵션을 사용한다. 자세한 내용은 JVM Options를 참조한다.
  • Young Generation의 크기를 줄인다. Young Generation의 크기가 작으면 상대적으로 적은 수의 Object들이 Promotion되고 그 만큼 Promotion Failure가 발생할 확률이 줄어든다.

아래에 Promotion Failure가 발생한 상황의 GC Log 예제가 있다.

197.976: [GC 197.976: [ParNew: 260872K->260872K(261952K), 0.0000688 secs]
197.976: [CMS197.981: [CMS-concurrent-sweep: 0.516/0.531 secs]
  (concurrent mode failure): 402978K->248977K(786432K), 2.3728734 secs] 
   663850K->248977K(1048384K), 2.3733725 secs]