본문 바로가기

C#/Knowledge

GC 가비지 컬렉터

1-1. 위의 코드가 문제가 되는 이유를 메모리 관점에서 설명해주세요.

ⓐ 문자열의 불변성

  • C#에서 문자열은 불변(immutable)이다. 즉, 한 번 생성된 문자열은 변경될 수 없다.
    문자열에 새로운 내용을 추가할 때마다 새로운 문자열 객체가 생성된다.
  • LogMessages에 메시지를 추가할 때마다, 새로운 문자열 객체가 생성되고 기존 문자열의 내용과 새로 추가된 문자열의 내용이 모두 복사된다. 이는 메모리 사용을 비효율적으로 만든다.

ⓑ 메모리 재할당과 복사

  • LogMessages가 길어질수록 새 문자열을 만들 때 필요한 메모리도 늘어나게 된다. 기존 문자열이 1000자의 길이를 가지고 있다면, 새 문자열을 생성하기 위해 기존 1000자와 새로 추가될 문자열의 길이만큼의 메모리가 필요하다.
  • 이는 반복될수록 더 많은 메모리가 할당되고, 기존 문자열을 새 문자열로 복사하는 작업이 반복되므로 성능이 저하된다.

ⓒ GC(가비지 컬렉션) 오버헤드

  • 메모리 재할당과 복사가 반복되면서 많은 임시 문자열 객체가 생성되고 소멸된다.
    이러한 객체들은 가비지 컬렉터에 의해 회수되어야 하므로, 가비지 컬렉션의 빈도가 증가하게 된다.
    이는 프로그램의 성능에 부정적인 영향을 미칠 수 있다.

ⓓ 스케일링 문제

  • 예제 코드에서는 10,000번의 로그 메시지를 추가하고 있다. 실제 애플리케이션에서는 이보다 더 많은 로그가 필요할 수 있다. 이 경우 위와 같은 메모리 재할당 문제는 더 심각해질 수 있다.
더보기

이를 개선하기 위해서는 StringBuilder를 사용하는 것이 좋다.
StringBuilder는 가변(mutable) 문자열을 사용하므로, 문자열을 추가할 때마다 새로운 객체를 생성하지 않고 기존 버퍼에 문자를 추가하기 때문에 메모리 사용이 효율적이다.

 

2-1. 가비지 컬렉터란 무엇인가요?

가비지 컬렉터(Garbage Collector, GC)는 프로그램에서 더 이상 사용되지 않는 메모리(객체)를 자동으로 회수하여 메모리 누수를 방지하고 효율적인 메모리 관리를 도와주는 시스템이다. C#을 포함한 여러 현대 언어들(C#, Java 등)은 자동 메모리 관리를 위해 가비지 컬렉터를 사용한다. GC는 메모리를 정기적으로 검사하여 참조되지 않는 객체를 찾아내고 이를 회수하여 메모리 풀이 다시 사용될 수 있게 한다.

2-2. 가비지 컬렉터의 장점과 단점에 대해 설명해주세요.

   장점

  1. 자동 메모리 관리:
    • 프로그래머가 직접 메모리를 해제하지 않아도 되므로 메모리 관리가 용이해진다.
  2. 메모리 누수 방지:
    • 사용되지 않는 메모리를 자동으로 회수하여 메모리 누수를 방지한다.
  3. 안정성 향상:
    • 잘못된 메모리 접근(예: 이중 해제, 해제 후 접근 등)으로 인한 버그를 줄일 수 있다.

   단점

  1. 성능 오버헤드:
    • 가비지 컬렉션 작업은 프로그램 실행 중에 발생하므로, 일시적인 성능 저하를 초래할 수 있다.
  2. 예측 불가능한 타이밍:
    • GC는 언제든지 작동할 수 있으므로, 실시간 시스템에서 예측하지 못한 타이밍에 성능 저하가 발생할 수 있다.
  3. 메모리 사용 증가:
    • GC가 작동하기 전까지는 사용되지 않는 객체들이 메모리를 차지하고 있을 수 있다.

 

2-3. 가비지 컬렉터의 세대 개념에 대해 설명해주세요.

가비지 컬렉터는 효율성을 높이기 위해 객체를 세대(generation)로 분류하여 관리한다.
C#의 GC는 일반적으로 세 가지 세대를 사용한다.

세대 0 (Generation 0):

  • 가장 최근에 생성된 객체들이 위치합니다. 객체의 생존 시간이 짧을 것이라고 가정한다.
  • 세대 0의 가비지 컬렉션이 가장 빈번하게 일어난다.

세대 1 (Generation 1):

  • 세대 0에서 살아남은 객체들이 승격되는 세대입니다. 중간 정도의 생존 시간을 가진 객체들이 위치한다.

세대 2 (Generation 2):

  • 세대 1에서 살아남은 객체들이 승격되는 세대입니다. 장기 생존 객체들이 위치한다.
  • 가장 드물게 가비지 컬렉션이 일어난다.

세대 개념을 사용하면 단기 생존 객체와 장기 생존 객체를 구분하여 가비지 컬렉션의 빈도와 비용을 최적화할 수 있다.

 

2-4. 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요?

박싱(Boxing):

  • 값 타입(예: int, bool 등)을 힙에 할당된 객체로 변환하는 과정
  • 박싱이 발생하면 값 타입이 객체로 변환되어 힙에 저장되고, 메모리 할당 및 복사 비용이 발생

언박싱(Unboxing):

  • 힙에 할당된 객체를 다시 값 타입으로 변환하는 과정
  • 언박싱 과정에서는 힙에서 값을 읽어와 스택에 복사하는 작업이 필요

주의해야 할 점:

성능 문제

  • 박싱과 언박싱은 메모리 할당 및 복사가 필요하기 때문에 성능 오버헤드가 발생할 수 있다.
    따라서 빈번한 박싱과 언박싱을 피하는 것이 좋다.

명시적 변환

  • 언박싱 시에는 명시적인 타입 변환이 필요하므로,
    잘못된 타입으로 언박싱하려고 하면 InvalidCastException이 발생할 수 있다.

불필요한 박싱 방지

  • 컬렉션 사용 시, 제네릭을 활용하여 박싱을 피할 수 있다.
    예를 들어, List<int>를 사용하면 박싱이 발생하지 않지만, ArrayList를 사용하면 박싱이 발생할 수 있다.

 

2-5. 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요?

오브젝트 풀(Object Pool)은 객체를 생성하고 재사용할 수 있도록 하는 디자인 패턴이다.
필요한 객체를 새로 생성하지 않고, 미리 생성된 객체를 재사용함으로써 메모리 사용과 성능을 최적화할 수 있다.

  1. 객체 생성 비용 감소:
    • 객체를 반복적으로 생성하고 소멸하는 대신, 재사용 가능한 객체를 유지함으로써 객체 생성 및 소멸에 따른 비용을 줄일 수 있다.
  2. 메모리 할당 감소:
    • 힙 메모리 할당이 줄어들어 메모리 단편화와 가비지 컬렉션 횟수를 줄일 수 있다.
  3. 성능 향상:
    • 재사용 가능한 객체를 제공함으로써 응답 시간을 단축하고 애플리케이션의 전반적인 성능을 향상시킬 수 있다.
  4. 안정성 증가:
    • 객체 생성 실패(예: 메모리 부족)에 대한 위험을 줄이고, 안정적인 객체 할당을 보장할 수 있다.

오브젝트 풀은 특히 빈번하게 생성되고 소멸되는 객체가 있는 경우에 유용하다.
예를 들어, 네트워크 연결, 스레드, 데이터베이스 연결과 같은 자원은 오브젝트 풀을 통해 효율적으로 관리할 수 있다.

 

'C# > Knowledge' 카테고리의 다른 글

선형 자료 구조 - LinkedList, Stack, QueQue  (0) 2024.07.16
C# 심화 문법  (0) 2024.07.11
상속과 인터페이스  (0) 2024.07.09
스택 메모리 vs 힙 메모리  (0) 2024.07.09
콜백, delegate, event  (0) 2024.07.04