1. GC란 무엇인가?
Garbage collector는 문서에 다음과 같이 정의되어 있습니다.Garbage collector는 다음과 같이 자동으로 동적 메모리 관리를 수행하는 것이다.
- 운영체제로부터 메모리를 할당하고, 또 할당 받은 메모리를 돌려준다.(응?)
- 애플리케이션에게 요청받은 메모리를 나눠줍니다(응?)
- 애플리케이션에서 메모리의 어느 부분이 여전히 사용하는지 판단한다.
- 사용하지 않는 메모리를 애플리케이션에서 다시 사용할 수 있도록 정리한다.
아니, Garbage collector는 메모리를 청소하는 놈 아닙니까?
네 저도 그렇게 알고 있었습니다만, 문서를 보니 할당과 해제를 모두 관리하는 놈이었군요(흠.. 인터레스팅)
계속해서 HotSpot Garbage collector는 그러한 것들의 동작을 효과적으로 하기 위해 다음과 같은 다양한 기술을 사용한다고 합니다.(별거 없음 주의)
- 객체의 수명에 따른 세대 분리로 가장 많이 정리할 가능성이 있는 메모리 영역을 포함한 heap 영역에 집중함.
- 여러 thread를 사용하여 병렬로 동작하거나 애플리케이션 백그라운더에서 동시에 오래동안 동작을 수행한다.
- 살아남은 객체를 밀집 시킴으로써 여유 있는 공간들이 연속적으로 이어진 더 큰 공간이 되도록 함
자 그럼, 왜 적절한 Garbage Collector 선택이 중요할까요?
Garbage collector의 목적은 동적 메모리 관리를 애플리케이션 개발자가 하지 않도록 하기 위함입니다. C의 포인터를 써보신 분은 알 것입니다. alloc - free를 개발자가 명시적으로 함으로써 받게되는 스트레스를. 물론 Java 개발자들은 "개발은 편하지만 관리는 아니다"라는 점을 깨달으실겁니다. 세상의 공짜는 없으니까요. 어찌되었든, 개발자는 동적 메모리의 할당과 해제를 매우 세심하게 객체의 생명주기를 관리하면서 개발할 필요는 없어졌다 이 말입니다. 추가적인 런타임 오버헤드(GC 작업으로 인한)라는 비용을 떠안은 대신, 메모리 관리와 관련된 에러를 완전히 제거한 Trade-off의 결과죠.
자 그렇다면, Garbage collector는 언제 문제가 될까요? 몇몇 애플리케이션에서는 절대 문제가 되지 않습니다. 즉, 몇몇 애플리케이션에선 Garbage collection으로 인해 치명적이지 않은 횟수와 시간동안 잠시 멈출 뿐이기 때문에, 잘 동작할 수 있습니다. 그러나, 많은 클래스를 가지고 있는 애플리케이션, 특히 아주 많은 데이터(수 GB급), 많은 Thread와 많은 트랜잭션이 반복되는 그러한 애플리케이션에서는 그렇지 않습니다. 경험상, 지금 이 글을 보고 있을 현업자분들은 거의 모두 어느 정도는 신경을 쓰며 개발해야하며, 이 GC의 튜닝 경험이 경력의 척도가 되기도 합니다.
Amdahl('암달'이라고 읽음)의 법칙을 아십니까? 뜬금없이 웬 Amdahl이냐고요? Amdahl의 법칙에 따르면, 주어진 문제에 있어 병렬 처리 속도의 향상은 문제의 순차적인 부분에 의해서 제한된다고 합니다. 무슨 의미냐구요? 대부분의 부하는 완벽하게 병렬처리화 될 수는 없다는 겁니다. 일부는 항상 순차적으로 해야하기때문에 병렬처리로 항상 해결할 수 있는게 아니라는 말이죠. Java 플랫폼에서는 현재 네 가지의 Garbage collection이 지원되며, Serial GC만 빼고 나머지는 병렬처리로 성능을 향상 시킬수 있으나, Garbage collection작업을 하는 동안의 오버헤드를 가능한 낮게 하는게 정말 중요합니다.
Figure 1-1은 Garbage collection의 overhead와 프로세서 수에 따른 Throughput의 저하를 보여줍니다.
이 도표는 작은 시스템을 개발할 때의 throughput 문제는 무시할 정도였지만 큰 시스템으로 scale-up될 때 주요 병목이 될 수도 있다는 것을 보여줍니다. 그러나 병목을 제거하는 작은 개선이 큰 성능 향상을 가져올 수 있다고 볼 수도 있습니다. 충분히 큰 시스템은 올바른 Garbage collector를 선택하고 필요하다면 적절히 튜닝하는 것이 가치가 있다는 것을 말하는 것이죠.
Serial collector는 보통 매우 작은 애플리케이션에 적합합니다. 특히 현대의 프로세서 수준에서 약 100MB 까지의 heap을 요구하는 시스템말이죠. (엔터프라이즈 애플리케이션에서는 과연 이 정도만를 요구하는 시스템이 있을까 모르겠습니다만...) 나머지 collector들은 추가적인 오버헤드와 복잡성을 가지고 있는데 이는 특화된 동작 방식에 대한 비용입니다. 만약 특화된 동작 방식이 필요없는 애플리케이션이라면 그냥 serial collector를 쓰시면됩니다. Serial collector가 적합하지 않을 때가 언제냐면, 많은 양의 메모리와 2개 이상의 프로세서 환경에서 크고 무거운 쓰레드들이 있는 애플리케이션입니다. 애플리케이션이 서버일 때는 G1 collector가 기본 collector입니다(Java9부터 적용되는 이야기입니다. Java9이전은 parallel collector가 기본 collector입니다)
![]() |
| Figure 1-1 |
위 도표를 보았을 때 저처럼 의문을 가지는 사람이 있을겁니다. 프로세서가 많은데 왜 Throughput이 더 떨어지지? 왜냐면 그게 Amdahl의 망할 법칙때문이거든요. Throughput은 처리"율"을 의미합니다. 아무리 병렬처리를 하더라도, 순차적으로 처리해야하는 부분은 병렬처리로도 답이 없기때문에 무한정 processor를 늘리더라도 오히려 효율은 떨어지는 셈인거죠. GC 시간이 1%대 정도의 어플리케이션이라면 processor가 2개일때보다 32개일 때 효율이 약 20%가 손실되는 셈입니다. GC시간이 30%를 차지하는 (이미 망한) 애플리케이션은 더 처참하죠. Throughput의 90%가 손실됩니다.
이 도표는 작은 시스템을 개발할 때의 throughput 문제는 무시할 정도였지만 큰 시스템으로 scale-up될 때 주요 병목이 될 수도 있다는 것을 보여줍니다. 그러나 병목을 제거하는 작은 개선이 큰 성능 향상을 가져올 수 있다고 볼 수도 있습니다. 충분히 큰 시스템은 올바른 Garbage collector를 선택하고 필요하다면 적절히 튜닝하는 것이 가치가 있다는 것을 말하는 것이죠.
Serial collector는 보통 매우 작은 애플리케이션에 적합합니다. 특히 현대의 프로세서 수준에서 약 100MB 까지의 heap을 요구하는 시스템말이죠. (엔터프라이즈 애플리케이션에서는 과연 이 정도만를 요구하는 시스템이 있을까 모르겠습니다만...) 나머지 collector들은 추가적인 오버헤드와 복잡성을 가지고 있는데 이는 특화된 동작 방식에 대한 비용입니다. 만약 특화된 동작 방식이 필요없는 애플리케이션이라면 그냥 serial collector를 쓰시면됩니다. Serial collector가 적합하지 않을 때가 언제냐면, 많은 양의 메모리와 2개 이상의 프로세서 환경에서 크고 무거운 쓰레드들이 있는 애플리케이션입니다. 애플리케이션이 서버일 때는 G1 collector가 기본 collector입니다(Java9부터 적용되는 이야기입니다. Java9이전은 parallel collector가 기본 collector입니다)

댓글 없음:
댓글 쓰기