[c#] Garbage Collection

알감자·2022년 3월 29일
0

게임공부

목록 보기
5/22

1. Garbage Collection이란?

  • 쓰레기 수집은 동적 할당된 메모리 영역 가운데 더 이상 사용할 수 없게 된 영역을 탐지하여 자동으로 해제하는 기법이다. 더 이상 사용할 수 없게 된 영역이란, 어떤 변수도 가리키지 않게 된 영역을 의미한다.

2. 동작원리

1) 포인터 추적 방식

  • 포인터 추적 방식은 한 개 이상의 변수가 접근 가능한 메모리는 앞으로 사용할 수 있는 메모리로 간주하고, 그 밖의 메모리를 해제하는 방식을 가리킨다.

i) mark and sweep

  • 표시하고 쓸기 기법은 포인터 추적 기법 가운데 가장 단순한 기법이다.

  • 먼저 각 메모리 할당 영역에 표시를 위해 1 비트의 메모리를 남겨 둔다.

  • 표시 단계에서, 모든 변수가 가리키는 영역을 "사용 중"으로 표시하고, 그 영역에서 가리키는 또다른 영역 또한 "사용 중"으로 표시한다.

  • 이와 같이 모든 메모리 영역을 표시하고 나면, 표시되지 않은 영역을 접근 불가능한 메모리 영역이 된다. 접근 불가능한 메모리 영역들을 쓸기 단계에서 모두 해제한다.

  • 단점은, 표시 단계에서 메모리 내용이 변경되지 않아야 하기 때문에 전체 시스템의 실행이 정지된다는 것이다. 또한 전체 메모리 영역을 검사해야 하므로 메모리 페이징을 사용하는 운영체제에서 프로그램의 성능이 저하될 수 있다.

ii) 삼색 표시 기법

  • 표시하고 쓸기 기법의 단점을 보완하기 위해, 많은 언어들은 삼색 표시 기법을 사용한다. 삼색 표시 기법은 기본적으로 표시하고 쓸기와 같은 기법이지만, 표시 단계에서 2가지가 아닌 3가지(흰색, 회색, 검은색) 정보 중 하나로 메모리를 표시한다. 이 기법은 다음과 같은 순서로 이루어진다.
  1. 각각의 객체를 흰색, 회색, 검은색으로 분류한다.
  • 흰색은 더이상 접근 불가능한 객체를 가리킨다.
  • 회색은 접근 가능한 객체이지만, 이 객체에서 가리키는 객체들은 아직 검사되지 않았음을 의미한다.
  • 검은색은 이 영역에서 가리키는 객체들이 흰색 객체를 가리키지 않음을 의미한다.
  • 알고리즘이 시작할 때는 변수가 가리키는 객체들이 회색으로 표시되며, 그 외의 모든 객체는 흰색으로 표시된다.
  1. 회색으로 표시된 객체 가운데 하나를 선택하여 검은색으로 표시하고, 이 객체가 가리키는 모든 객체를 회색으로 표시한다.

  2. 회색 객체가 하나도 남지 않을 때까지 위 과정을 반복한다.

  3. 남은 흰색 객체는 접근 불가능한 객체이므로, 모두 해제한다.

  • 이 알고리즘은 단순한 표시하고 쓸기 알고리즘과 달리, 프로그램이 실행 중에도 병행하여 수행할 수 있다. 또한, 메모리가 고갈되었을 때 쓰레기 수집을 실행하는 것이 아니라 주기적으로 수집하는 것도 가능하다.


    iii) 객체 이동 기법

  • 객체 이동 기법은, 해제할 객체 표시가 완료된 후 해제되지 않은 객체를 그대로 두는 것이 아니라, 다른 영역으로 복사하는 기법을 가리킨다. 원래대로 유지해도 무방한 객체를 복사하는 것은 언뜻 비효율적으로 여겨질 수도 있으나, 다음과 같은 실용적인 장점을 가지고 있다.

    • 해제된 후 재사용 가능한 영역과 사용 중인 영역을 표시하기 위해 추가적인 작업을 할 필요가 없다. 따라서 해제된 영역을 포인터로 관리하는 방식에 비해 할당과 해제가 빠르게 이루어진다.

    • 할당된 메모리들이 단편화되는 것을 막을 수 있다.

    • 연결 리스트와 같은 연결형 자료구조에서, 서로 연결된 객체들이 메모리 상에서 가까운 위치에 할당될 확률이 높아진다. 이는 캐시와 관련하여 성능 향상에 도움이 된다.

  • 반면, 메모리 이동 기법은 주기적으로 포인터의 내용이 바뀌므로 포인터 연산을 사용할 수 없게 된다는 단점이 있다.


iiii) 세대 단위 쓰레기 수집

  • 많은 연구자들은 프로그램에서 새롭게 할당된 영역일수록 금방 해제될 확률이 높다는 관찰을 보고하였다.

  • 세대 단위 쓰레기 수집 기법은 이런 특성을 이용하여, 각각의 객체를 할당된 시간에 따라 세대별로 구분하여, 각 세대별로 서로 다른 메모리 영역에 객체를 할당한다.

  • 만약 한 세대의 메모리 영역이 꽉 차면, 이 메모리 영역에서 살아남은 객체를 더 오래된 메모리 영역으로 옮긴다.

  • 새로 할당된 영역에서는 대부분의 객체들이 빠르게 해제되고 오래된 영역에서는 객체들이 변하지 않을 확률이 높으므로, 이 기법은 메모리의 일부 영역만을 주기적으로 수집하게 되는 장점이 있다.

  • 자바, 닷넷 프레임워크 등 현대적 언어들은 대부분 이 기법을 사용한다.



2) 참조 횟수 계산 방식

  • 일부 쓰레기 수집 기법은 참조 횟수 계산 방식을 사용한다. 참조 횟수 계산 방식은 각 객체에서 참조 횟수를 기억하여, 참조 횟수가 0이 되면 해당 객체를 해제하는 방식을 가리킨다.

  • 파이썬 표준 구현인 CPython에서 이 방식을 사용한다.

  • C++에서는 스마트 포인터라는 특수한 객체를 이용해 이 기법을 구현할 수 있다.

  • 참조 횟수 계산 방식은 다음과 같은 장점을 가지고 있다.

    • 객체가 접근 불가능해지는 즉시 메모리가 해제되므로, 프로그래머가 객체의 해제 시점을 어느 정도 예측할 수 있다.
    • 객체가 사용된 직후에 메모리를 해제하므로, 메모리 해제 시점에 해당 객체는 캐시에 저장되어 있을 확률이 높다. 따라서 메모리 해제가 빠르게 이루어진다.
  • 위와 같은 장점 때문에, 참조 횟수 계산 방식은 메모리 관리 뿐 아니라 다른 자원 할당 기법에도 종종 사용된다.

  • 예를 들어 하드 디스크 블록의 할당과 해제를 담당하는 파일시스템의 경우, 포인터 추적 방식의 쓰레기 수집은 디스크라는 매체의 특성 상 오랜 시간을 소모하게 된다.

  • 그러나 참조 횟수 계산 방식은 할당된 블록을 해제하는 시점에 해당 블록을 가리키는 포인터가 운영체제의 버퍼에 로딩되어 있으므로, 빠르게 블록을 해제할 수 있다.

  • 반면 참조 횟수 계산 방식에는 다음과 같은 단점이 있다.

    • 두개 이상의 객체가 서로를 가리키고 있을 경우, 참조 횟수가 0이 되지 않게 된다. 이를 순환참조라고 하며, 메모리 누수의 원인이 된다. CPython은 이 문제를 해결하기 위해 순환 참조를 감지하는 알고리즘을 사용한다. 또한 자료구조에서 약한 참조(참조 횟수를 증가시키지 않는 포인터)를 사용하여 이 문제를 해결할 수도 있다.

    • 멀티스레드 환경에서는, 스레드간에 공유하는 객체의 참조 횟수 계산을 위해 원자적 명령을 사용하거나 락을 걸어야 한다. 이 문제를 회피하기 위해 스레드 단위 지역 변수로 참조 횟수를 따로 관리하면서, 스레드의 참조 횟수가 0이 될때만 전역 참조 횟수를 확인하는 방식을 사용할 수 있다. 리눅스 커널에서 이 방식을 사용한다.

    • 참조 횟수가 0이 될때, 해당 객체가 가리키는 다른 객체들 또한 동시에 0으로 만드는 작업이 일어난다. 이 과정은 경우에 따라 많은 시간이 걸릴 수도 있기 때문에 실시간 시스템에는 적합하지 않을 수 있다.

    출처 : https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)


3. C# Garbage Collection Interview Question

  1. What is Garbage Collection?
  2. What are strong references and weak references in GC?
  3. How GC come to know that object is ready to get collected?
  4. What are generations in GC?
  5. How value types get collected v/s reference types?
  6. Dispose v/s Finalize
  7. How to manage Unmanaged resources?
  8. Does GC work similar for Web Application and windows application? Is there any difference?
  9. How to Force Garbage Collection?
  10. Why does Garbage Collection only sweep the heap?

1) What is Garbage Collection


  • 프로그램이 시작되면 시스템에서 프로그램이 실행될 수 있도록 일부 메모리를 할당한다.
    C# 프로그램이 클래스를 인스턴스화하면 개체가 만들어진다.
    프로그램이 개체를 조작하고 어떤 시점이 되면 개체가 더 이상 필요하지 않을 수 있다.
    개체가 프로그램에 더 이상 액세스할 수 없게 될 때 garbage collection 후보가 된다.

  • Garbage Collector는 heap에서 새 개체를 구성할 공간이 부족한 경우에만 정리를 시작한다.
    Stack은 함수가 끝나면 자동적으로 정리된다. CLR이 이것을 담당하기 때문에 개발자는 신경 쓸 필요가 없다.

  • 힙은 Garbage Collector에서 관리한다.

  • Garbage Collector가 없는 관리되지 않는 환경에서는 heap에 할당된 개체를 추적해야 하며 명시적으로 해제해야 한다. .NET 프레임워크에서 이 작업은 Garbage Collector에서 수행된다.


2) What are strong references and weak references in GC?

  • 응용 프로그램(Application)의 코드가 해당 개체에 도달할 수 있는 동안 Garbage Collector는 응용 프로그램에서 사용하는 개체를 수집할 수 없다. 응용 프로그램은 개체에 대한 강력한 참조(Strong reference)를 가지고 있다고 한다.

  • 약한 참조(Weak reference)는 응용 프로그램이 개체에 액세스할 수 있도록 하면서 Garbage Collector가 개체를 수집할 수 있도록 허용한다. 약한 참조는 강력한 참조가 없을 때 개체가 수집될 때까지 확정되지 않은 시간 동안(the indeterminate amount of time)에만 유효하다. 약한 참조를 사용하여 응용 프로그램은 아직 수집되지 않는 개체에 대한 강한 참조를 얻을 수 있다.
    (When you use a weak reference, the application can still obtain a strong reference to the object, which prevents it from being collected.)0
    그러나 강력한 참조가 다시 설정되기 전에 가비지 수집기가 먼저 개체에 도착할 위험이 항상 있다.

  • 약한 참조는 많은 메모리를 사용하는 개체에 유용하지만 Garbage Collector에 의해 회수되면 다시 쉽게 만들 수 있다.


3) How GC come to know that object is ready to get collected?

  • GC는 일반적으로 개체에 연결할 수 없는 경우 개체를 수집한다.(i.e. not in reference)
    Garbage Collector는 다음 정보를 사용하여 개체가 살아있는지(live) 여부를 결정한다.

    • Stack roots : JIT(just-in-time) 시간 별 컴파일러 및 스택 워커에서 제공하는 스택 변수
    • Garbage collection handles : 관리되는 개체를 가리키고 사용자 코드 또는 일반적인 언어 런타임에 의해 할당할 수 있는 개체를 처리
    • Static data : 다른 개체를 참조할 수 있는 응용 프로그램 도메인의 정적 개체. 각 응용 프로그램 도메인은 정적 개체를 추적한다.

4) What are generations in GC?

  • 0세대 : 가장 젊은 세대이며 수명이 짧은 개체가 포함되어 있다. 수명이 짧은 개체의 예는 임시 변수이다. 가비지 수집은 이 세대에서 가장 자주 발생한다.

  • 1세대 : 이 세대는 수명이 짧은 객체를 포함하고 단명한 개체와 수명이 긴 객체 사이의 버퍼 역할을 한다.

  • 2세대 : 이 세대에는 수명이 오래 지속되는 개체가 포함되어 있다. 수명이 긴 개체의 예는 프로세스 기간 동안 라이브정적 데이터를 포함하는 서버 응용 프로그램의 개체다.


<5, 6 번 질문은 생략>


7) Can we pin objects for later references?

  • CLR을 사용하면 가비지 수집 중에 오브젝트가 이동되지 않도록 개체를 "고정"(pin)할 수 있다. 그러나 이것은 잠재적으로 Garbage Collection에 큰 영향을 미칠 수 있다. 패스(pass) 중에 오브젝트가 고정되어 있는 경우에도 힙이 조각화된다(fragmented). 더구나 패스 후 응모를 받을 자격이 있는 개체가 있으면 gen-1로 이동해야 했음에도 불구하고 여전히 gen-0 개체로 간주된다.

  • C#은 fixed statement를 사용하여 객체를 고정할 수 있게한다. fixed statement는 Garbage Collector가 이동식 변수를 재배치하지 못하게한다. fixed statement는 안전하지 않은 컨텍스트에서만 허용된다.


8) Does GC work similar for Web Application and windows application? Is there any difference?

  • GC는 동시(Concurrent) 또는 동시가 아닐 수 있는(Non-concurrent) Window Application의 "Workstation Mode"에서 작동한다. Concurrent garbage collection을 사용하면 관리되는 스레드가 Garbage Collection 중에 작업을 계속할 수 있다.

  • .NET Framework 4부터 배경 가비지 컬렉션(background garbage collection)은 동시 가비지 컬렉션(concurrent garbage collection)을 대체한다.

  • 웹 응용 프로그램의 경우 높은 처리량과 확장성이 필요한 서버 응용 프로그램을 위한 "Server Garbage Collection" 모드에서 작동한다. 서버 가비지 수집은 non-concurrent 또는 background 일 수 있다.


9) How to force garbage collection?

  • You can force this by adding a call to GC.Collect.

    • ex)
      StreamWriter stream = File.CreateText(“temp.dat”);
      stream.Write(“some test data”);
      GC.Collect();
      GC.WaitForPendingFinalizers();
      File.Delete(“temp.dat”);

10) Why does Garbage Collection only sweep the heap?

  • Garbage Collector는 스택을 스캔한다 - Stack의 things에 의해 heap안에 현재 사용 중인 것(가리키는 - pointed to)이 무엇인지 확인한다.

  • 스택이 그런 식으로 관리되지 않기 때문에 Garbage Collector가 스택 메모리를 수집하는 것을 고려하는 것은 의미가 없다: 스택의 모든 것이 "사용 중"으로 간주된다. 메서드 호출에서 반환하면 스택에서 사용하는 메모리가 자동으로 회수된다. 스택 공간의 메모리 관리는 너무 간단하고 저렴하며 쉽기 때문에 가비지 수집이 관여되는 것(involved)을 원하지 않을 것이다.


출처 : https://www.csharpstar.com/interview-questions-garbage-collection-csharp/

  • 가비지 컬렉션이 동작하는 때를 아는 방법
    finalizers? -> 불리는 정확한 시간은 알 수 없다,,

https://blog.shiren.dev/2021-08-30/

0개의 댓글