Post List

2018/01/19

C# __6.0 가비지컬렉터(GarbageCollector)사용시 주의해야 할 점

이전 내용에서 가비지 컬렉션에 대해 알아보았으니, 이번에는 비지 컬렉터의 성능을 저하시키지 않기 위해 객체를 생성함에 있어서 조심해야할 부분 살펴보겠습니다.

첫번째로 객체를 너무 많이 할당하지 말아야 합니다. 
가장 기본적인 지침인데요, CLR 객체 할당 속도가 빠르긴 하지만 너무 많은 수의 객체는 관리되는 힙의 세대에 메모리 포화를 초래할 있고, 이는 빈번한 가비지 컬렉션을 부르는 결과를 낳습니다. 객체 할당 코드를 작성할 필요한 객체인지와 필요 이상으로 많은 객체를 생성하는 코드가 아닌지의 여부를 고려해야합니다.


두번째는 너무 객체 할당을 피해야 합니다. 
CLR 보통 크기의 객체를 할당하는 힙과는 별도로 85KB 이상의 객체를 할당하기 위한 '대형 객체 (LOH:Large Object Heap)' 따로 유지합니다. 우리가 평소에 사용하는 힙은 대형 객체 힙에 대비되는 개념으로 소형 객체 이라고 부르기도 합니다.

커다란 객체를 소형 객체 힙에 할당한다면 0세대가 빠르게 차오르게 되므로 가비지 컬렉션을 자주 촉발하게 되고, 이는 어플리케이션의 성능 저하를 불러오게 됩니다. 그래서 대형 객체 힙을 별도로 유지함으로 성능 향상의 효과를 내고 있습니다만, 단점도 있겠지요.

우선 대형 객체 힙은 동작 방식이 소형 객체 힙과 다릅니다. 대형 객체 힙은 객체의 크기를 계산한 그만한 여유 공간이 있는지 힙을 탐색하여 할당합니다. GC 수행한 소형 객체 힙은 해제된 메모리 공간에 인접 객체들을 끌어당겨 차곡차곡 정리하지만, 대형 객체 힙은 해제된 공간을 그대로 둔답니다. 매우 메모리를 복사하는 비용이 너무 비싸기 때문이지요.
공간은 나중에 다른 객체들에게 할당 되겠지만, 메모리를 0바이트의 낭비도 없이 사용하는 소형객체 힙과는 달리 공간을 군데군데 낭비하게 됩니다.

그리고 또하나 CLR 대형 객체 힙을 2세대 힙으로 간주하기 때문에 대형 객체 힙에 있는 쓰레기 객체가 수거되려면 2세대에 대한 가비지 컬렉션이 수행되어야 합니다. 2세대 가비지 컬렉션은 세대에 대한 가비지 컬렉션을 유발하기 때문에 수거되는 메모리의 양이 수록 어플리케이션이 정지되는 경우가 발생하게 됩니다. 그래서 너무 객체를 할당하는 코드는 조심스럽게 고려해보아야 한답니다.


세번째는 너무 복잡한 참조 관계는 자제해야 합니다
이러한 지침은 성능 뿐아니라 가독성을 위해서라도 주의하셔야합니다. 참조 관계가 많은 객체는 가비지 컬렉션 후에 살아남았을 때가 문제입니다. 가비지 컬렉션에서 살아남았을 경우 다음 세대로 옮기기 위해 메모리 복사를 수행하는데, 참조관계가 복잡한 객체의 경우에는 단순히 메모리를 복사하는데서 끝나지 않습니다. 객체를 구성하고 있는 필드 객체 간의 참조 관계를 일일이 조사해서 참조하고 있는 메모리 주소를 전부 수정한답니다 클래스 구조가 간단했다면 메모리 복사만으로도 끝났을 일을 탐색과 수정까지 거치게 되는것이죠.


네번째는 루트를 너무 많이 만들지 말아야 합니다. 
가비지 컬렉터는 루트 목록을 순회하면서 쓰레기를 찾아냅니다. 루트 목록이 작아진다면 그만큼 가비지 컬렉터가 검사를 수행하는 횟수가 줄어들므로 빨리 가비지 컬렉션을 끝낼 있습니다. 따라서 루트를 가급적 많이 많들지 않는 것이 성능에 유리하겠지요. 내용은 필요 이상으로 객체를 만들지 말라는 기본적인 지침입니다.