Deff_Dev

[C#] 가비지 컬렉터 (Garbage Collectior) 본문

면접 질문 정리/C#

[C#] 가비지 컬렉터 (Garbage Collectior)

Deff_a 2024. 7. 8. 11:05
  1. 가비지 컬렉터란 무엇인가요?
    • 가비지 컬렉터는 CLR의 메모리 관리 소프트웨어로, 할당할 힙 메모리 공간이 부족할 때, 가비지 컬렉터가 힙 메모리를 돌며 사용하지 않는 메모리를 회수한다.
    • 참조형 변수를 선언하는 경우 stack영역의 주소값과 heap영역의 실제 값이 나뉘게 된다. 프로그램을 실행하던 중 stack영역의 값을 pop하는 경우 heap 영역의 실제 값은 쓰이지 않지만 메모리 공간에 남아있게 된다. 이를 자동으로 추적하여 메모리를 회수한다.
  2. 가비지 컬렉터의 장점과 단점에 대해 설명해주세요.
    • 프로그래머가 직접 메모리 해제를 안 해줘도 알아서 GC가 돌면서 메모리를 해제하기 때문에 편리하다.
    • 가비지 컬렉터가 실행되는 동안 프로그램의 성능이 일시적으로 저하될 수 있으며, 가비지 컬렉터는 힙 메모리가 부족할 때, 알아서 실행되므로 프로그래머가 실행 시점을 예측하기 어렵다.
  3. 가비지 컬렉터의 세대 개념에 대해 설명해주세요.
    • CLR은 메모리 구역을 나누어 메모리에서 빨리 해제될 객체와 오래 살아남을 것 같은 객체들을 구분한다. 이때, 0, 1, 2의 3개의 세대로 나눈다.
    • 0세대는 가비지 컬렉터를 한 번도 겪지 않은 객체들이 위치한다. 주로, 단명 객체들이 많아 가비지 컬렉터가 자주 발생한다.
      • 새로 힙 메모리를 할당하는 오브젝트 전부 → 살아남으면 1세대로 이동
    • 1세대에는 0세대를 통과하고 살아남은 객체들이 위치한다. 이 세대는 객체들이 조금 더 오래 생존할 가능성이 높다.
    • 2세대에는 가비지 컬렉션을 여러 번 겪고도 살아남은 오래된 객체들이 위치한다. 이 세대에서 가비지 컬렉션은 가장 드물게 발생하다.
      • 2세대까지 GC가 일어나면 모든 세대가 GC에 대상이 되는 것이고 이것을 Full GC라고한다.
      • Full GC → 프로그램을 멈추고 쓰레기 메모리를 찾아 해제
  4. 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요?
    • 박싱, 언박싱을 하는 과정에서 데이터를 메모리의 다른 위치로 이동시키는데, 이때, 추가적인 메모리 할당 및 해제 과정에서 힙에 쓰레기 메모리가 쌓여 GC에 부하를 줄 수 있다.
  5. 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요?
    • 오브젝트 풀(Object Pool)
      • 객체를 미리 생성해두고 필요할 때 재사용하며, 사용이 끝난 객체는 파괴하지 않고 풀에 반환하여 다시 사용할 수 있게 하는 디자인 패턴
    사용 이유
    • 오브젝트를 Destroy할 때, 오브젝트는 게임에서 삭제되지만, 메모리 상으로 미세한 쓰레기 메모리들이 쌓이기 때문에 생성/ 삭제를 자주할 때, 쓰레기 메모리가 쌓여 GC 호출이 자주 일어날 수 있다.
    • 오브젝트 풀을 이용하면 오브젝트를 사용하고 삭제할 때, 삭제가 아닌 비활성화 함으로써 해당 오브젝트를 계속 재사용하고, 삭제로 발생하는 쓰레기 메모리를 최소화하여 가비지 컬렉터 호출을 줄인다.
    • 그렇다고 해서 무조건 오브젝트 풀이 좋은 것은 아니니 생성/ 삭제가 자주 일어나는 오브젝트에 한해서 사용하는 것이 좋다.

확인 문제

using System;

public class Logger
{
    public string LogMessages { get; private set; }

    public Logger()
    {
        LogMessages = string.Empty;
    }

    public void Log(string message)
    {
        LogMessages += message + "\\n";
    }
}

public class Program
{
    public static void Main()
    {
        Logger logger = new Logger();
        
        for (int i = 0; i < 10000; i++)
        {
            logger.Log("This is log message number " + i);
        }

        Console.WriteLine("Logging completed. Total log length: " + logger.LogMessages.Length);
    }
}
  1. 위의 코드가 문제가 되는 이유를 메모리 관점에서 설명해주세요.
    • string은 참조 타입 객체이기 때문에, 문자열을 결합할 때마다 새로운 문자열 객체가 힙 메모리에 생성된다.
    • 많은 수의 문자열 객체가 생성되면서 GC 호출이 자주 일어날 수 있으며, 이 때문에 프로그램 성능에 영향을 미칠 수 있다.
  2. 아래와 같이 string이 아닌 StringBuilder가 권장되는 이유는 무엇일까요? 
public class Logger
{
    private StringBuilder logMessages;

    public Logger()
    {
        logMessages = new StringBuilder();
    }

    public void Log(string message)
    {
        logMessages.Append(message).Append("\n");
    }

    public override string ToString()
    {
        return logMessages.ToString();
    }
}
  • StringBulider를 사용하면 문자열을 조합할 때 마다 새로운 변수를 생성하지 않고 결합할 수 있기 때문에 string을 사용하는 것 보다 문자열 결합에 있어 메모리를 효율적으로 사용할 수 있다.