CMS 는 가비지 컬렉션 일시 중지 시간을 최소화하고, 애플리케이션 실행 중에 가비지 컬렉터와 프로세서 리소스를 공유할 수 있는 애플리케이션을 위해 설계되었습니다. 일반적으로 장기간 보존되는 데이터(tenured 크기가 큰 경우)가 비교적 많고 프로세서가 두 개 이상인 시스템에서 실행되는 애플리케이션은 이 컬렉터를 사용하면 이점을 얻을 수 있습니다. 하지만 일시 중지 시간이 짧은 모든 애플리케이션에 이 컬렉터를 고려해 볼 수 있습니다.
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html
CMS 는 STW 에 의한 정지 시간을 최소화시키는 컬렉터다. Concurrent-Mark-Sweep 의 약자다.
STW 정지 시간을 최소화하는 방법이 뭘까. 이름에서 힌트를 얻자면 '동시에' 한다는 것이다.
Garbage Collection 단계에서 STW 를 일으키는 단계는 크게 두가지다.
1. Marking : 살아있는 객체를 마킹하는 과정에서 객체 그래프가 변경되면 안된다.
2. Compact : 살아남은 객체의 메모리 파편화를 막기 위해 한 곳으로 모으는 과정이다. 객체의 위치를 옮겨야 하므로 애플리케이션을 멈추고 진행한다.
CMS 는 꼭 필요한 부분을 남기고 많은 부분을 STW 없이 유저 스레드와 동시에 진행하는 방법으로 일시 정지 시간을 줄였다.
-XX:+UseConcMarkSweepGC 로 활성화. Young Gen GC 는 ParNew 가 사용된.서버용 JVM 은 지연 시간을 줄이는 목표로 발전했다. 최근의 그러한 Garbage Collector 가 ZGC 이다.
지연 시간을 줄이는데 가장 크게 발목 잡는 것이 STW 이고, STW 를 줄이는 방법은 객체의 생성/삭제/이동을 유저 스레드의 동작과 안전하게 같이 진행하는 것이다.
CMS 이전의 GC(Serial, ParNew, PS..) 는 GC 모든 과정에서 STW 를 하였다. CMS 는 유저 스레드와 GC 의 일부를 동시에 실행시킨 첫 GC 로서 의미가 있으며, 어떻게 그렇게 했는지를 이해하면면 CMS 이후에 나온 GC 를 이해하기 더 쉽다.
Marking 단계중 CMS 가 유저 스레드와 동시에 실행하는 작업은 GC Root 로 부터 참조 체인을 따라가며 살아있는 객체에 마킹하는 작업이다. 루트 노드를 열거하는 작업은 여전히 STW 로 실행된다.
그러나 루트 노드 열거보다 참조 체인을 따라가는 작업이 훨씬 오래 걸리기에, 의미있는 발전이다.
또한 Compact 가 아닌 Sweep 이라서, 죽은 객체를 회수(Sweep)하는 작업을 유저 스레드와 동시에 진행할 수 있었다.

STW 루트 노드와 직접 연결된 객체만 mark 한다. 빠르게 끝남.
NO-STW 루트 노드와 직접 연결된 객체로 부터 객체 그래프를 탐색하며 mark 한다. 새로 생긴 객체는 증분 업데이트 방식에 따라 따로 기록해둔다.
STW. 증분 업데이트 방식에 의해 따로 기록한 객체를 반영한 mark 를 진행한다. 최초 표시보다 정지가 조금 더 길지만, 여전히 짧은 시간이다.
NO STW 유저 스레드가 실행되는 것과 동시에 죽은 객체를 회수한다.
유저 스레드와 동시에 GC 를 실행한다는 것은 하나의 시간에 CPU 처리량을 GC 가 뺏어간다는 이야기다.
CMS 는 (프로세서 코어 수 + 3)/4 개의 스레드 로 GC 를 진행한다. 프로세서 코어 수가 적으면 적을수록 GC 가 사용하는 자원의 비용이 커진다.
따라서 Oracle 문서 에서도 프로세서가 2개 이상인 환경에서 적합하다고 명시되어 있다.
CMS 는 유저 스레드와 동시에 실행된다.
따라서 Old Gen 의 공간이 100% 찼을때 GC 를 하면 안된다. 동시에 실행되는 스레드에 의해 만들어질 객체가 할당될 공간이 필요하기 때문이다.
특히 동시에 실행되는 유저 코드에 의해 한 객체가 죽었다 하더라도, 그 시점이 CMS 의 Marking 단계가 끝난 직후라면 다음 GC 까지 기다려야 한다. 이런 객체를 부유 쓰레기라 한다.
-XX:CMSInitiatingOccupancyFraction 옵션은 Old Gen 이 몇 % 차야 CMS GC 가 실행될지 설정할 수 있다. JDK6 부터 기본값은 92% 이다.
Old Gen 의 여유 공간이 8% 남게 되면 CMS GC 가 시작된다.
만약 8% 남은 상태에서 더 큰 공간의 할당 요청이 오면?
이 상황을 동시 모드 실패 라고 하며, CMS 는 애플리케이션을 멈추고 Serial Old GC 로 Major GC 를 실행한다.
이 동작은 매우 느리기 때문에 -XX:CMSInitiatingOccupancyFraction 옵션을 잘 조정하는게 중요하다.
mark-sweep 알고리즘의 한계이다. CMS 역시 직면해야하는 문제다.
메모리 파편화가 심해지면 전체 GC 를 실행해야 한다.
JDK 8 까지는 Foreground Collection 이라고 해서 -XX:+UseCMSCompactAtFullCollection, -XX:+UseCMSCompactAtFullCollection 옵션을 활용하여 Full GC 전 Compact 를 하여 파편화로 인한 메모리 부족을 해결할 수 있었다.
하지만 JDK-8010202에서 Foreground Collection 은 전체 GC 보다 더 오래 걸리고 문제가 많아서 JEP-214 에 의해 JDK9 부터는 사라졌다.
그래서 메모리 파편화는 결국 CMS 에서 근본적 문제로 남게 되었다.