가비지 컬렉터란 무엇인가요?
기본적으로 C#으로 작성된 코드는 관리 코드(Managed Code)에 해당됩니다. 관리 코드는 CLR(Common Language Runtime)의 관리하에 실행되며, 이 CLR이 메모리 할당, 보안, 스레드 등의 임무와 함께 더 이상 사용되지 않는 객체를 회수하는 역할까지 담당합니다. 바로 이 역할을 하는 것이 가비지 컬렉터입니다. 즉, 가비지 컬렉터는 불필요해진 객체의 메모리를 정리해주는 청소부와 같은 역할을 합니다.
하지만 모든 메모리가 가비지 컬렉터의 관리 대상은 아닙니다. C#에서도 비관리 코드(Unmanaged Code)를 작성할 수 있으며, 이는 unsafe
키워드를 사용하여 작성됩니다. 비관리 코드는 CLR이 제공하는 서비스(가비지 컬렉션 서비스 포함)를 받을 수 없습니다.
객체 할당과 수거 과정
C#에서 new object()
와 같이 객체를 생성하면 메모리가 할당됩니다. 코드가 실행되면서 변수에 객체가 할당되고, 더 이상 해당 객체를 참조하는 변수가 없어지거나 해당 객체에 접근할 수 없게 되면(예: 지역 변수가 스코프를 벗어나는 경우) 가비지 컬렉터의 수거 대상이 됩니다.
가비지 컬렉터는 객체가 사용 중인지 아닌지를 판단하기 위해 루트(Root)라는 개념을 사용합니다. 루트에는 스택에 있는 객체나 정적 필드 등이 포함됩니다. 루트에서부터 참조를 따라가며 도달 가능한 객체들은 사용 중인 것으로 간주하여 남겨두고, 루트로부터 도달 불가능한 객체들은 가비지 컬렉션 대상으로 판단하여 회수합니다. 소스에 나타난 그림처럼, 루트 목록에서 E와 B 객체는 더 이상 참조되지 않아 수거 대상이 되는 것을 볼 수 있습니다.
세대별 가비지 컬렉션 (Generational Garbage Collection)
.NET의 가비지 컬렉터는 효율성을 높이기 위해 세대(Generation) 개념을 사용합니다. 객체들은 생성된 시점에 따라 다른 세대에 할당되며, 일반적으로 새로 생성된 객체는 0세대(Generation 0)에 위치합니다.
가비지 컬렉터는 보통 0세대를 가장 자주 검사합니다. 0세대에서 수거되지 않고 살아남은 객체는 1세대(Generation 1)로 승격됩니다. 1세대에서 살아남은 객체는 2세대(Generation 2)로 승격됩니다. 2세대는 가장 오래된 객체들을 담고 있으며, 2세대 가비지 컬렉션은 비교적 드물게 발생합니다.
이러한 세대별 구조는 대부분의 프로그램에서 짧게 사용되고 버려지는 객체가 많다는 특성을 이용한 것입니다. 젊은 세대(0세대)를 자주 청소함으로써 전체적인 가비지 컬렉션 작업량을 줄이고 성능을 향상시킬 수 있습니다.
가비지 컬렉션을 이해한 후, 우리는 무엇을 해야 할까요?
가비지 컬렉터가 메모리 관리를 자동화해주지만, 개발자의 코드 작성 방식에 따라 가비지 컬렉션의 효율성과 애플리케이션의 성능이 크게 달라질 수 있습니다. 다음은 가비지 컬렉션의 부하를 줄이고 효율을 높이기 위한 몇 가지 팁입니다: