ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 가비지 컬렉터 가비지 컬렉션
    @ 16. 1 ~ 17. 1/C# 2016. 11. 19. 09:39


    * 객체 생존 기간
    객체는 new 연산자에 의해 메모리를 할당 하고 생성자에 의해 메모리에 있는 객체가 초기화 된다.
    반대로 소멸되는 경우에는 먼저 Finalize 메소드를 이용하여 메모리를 초기화 되지 않은 상태로 돌린다.

    다음으로 이메모리 공간을 Heap에 반환하는 것이다. 그러므로 객체의 존속기는 new를 이용하여 메모리를 할당받는 순간부터 메모리를 Heap에 반환 할 때 까지이다.

    * GC(Garbage Collector)
    C#의 경우 기존의 C/C++ 처럼(new, delete) 프로그래머가 메모리 관리를 하지 않아도 된다. CLR에서 자동으로 알아서 소멸 시켜 준다. 또한 C#의 경우 명시적으로 코드상에서 객체를 소멸 할 수 없는 대신 Garbage Collector라는 것을 지원 해 준다. Garbage Collector는 메모리에 있는 참조가 끝난 객체를 쓰레기 치우는 것처럼 소멸 시키는 역할을 하는 것 이다. 메모리가 부족 하다고 판단이 들면 Garbage Collector는 참조되는 않는 객체의 메모리 영역을 청소하여 Heap에 반환을 하는 것이다. 또한 Garbage Collector는 객체를 오직 한번만 제거해 버리며 참조되고 있지 않은것들만 제거 한다. 프로그래머다 일일이 코드를 통해 기술 했을때의 문제인 객체를 반복해서 소멸 한다든지, 참조되고 있는
    객체를 소멸한다든지 하는것은 막아 주는 것이다. 코딩 상에서 Garbage Collector에거 명시적으로 객체를 소멸 시켜 달라고 할 수 있으나 그것은 객체를 소멸 시켜도 된다는 것을 알리는 역할을 할 뿐이지 즉시 작동 하는 것은 아니다.

    * 소멸자의 사용

    - Finalize 메소드
      객체가 Garbage Collector에 의해 소멸되는 시점에 호출되는 함수 이다. 이곳에 파일을 닫는 다든지, DB접속을 종료 한다든지 하는 일들을 명시 할 수 있다. Finalize 메소드를 구현 하면 C# 컴파일러가 컴파일시 finalization   Queue에 이 객체가 등록되고 Garbage Collector가 작동되면 객체를 소멸하기 전에 Finalize 메소드를 호출하는 것이다.

    - 소멸자 사용방법

      Finalize 메소드 대신에 소멸자(~생성자)를 이용 할 수 있으며 컴파일을 하게 되면 소멸자를 Finalize 메소드로 바꾸어  놓게 된다. 즉 C#에서 소멸자와 Finalize 메소드는 같은 의미라고 보면 된다. 소멸자의 경우 항상 접근권한이 public  이 되어야 하므로 별도로 접근 지시자를 지정하지 못하도록 되어 있다. 또한 매개변수도 받을 수 없다.

    * System.CG 클래스

    Garbage Collector가 작동 하기 전에 리소스를 해제 할려고 하면 어떻게 할까? 아니다... 이경우 IDisposal 인터페이스이Dispose 메소드를 구현해 주고 사용자가 이메소드를 호출하면 직접 리소스를 해제 할 수 있다. 물론 Disposal 메소드안에 리소스를 정리하는 코드를 기술하면 된다. 민약 리소스를 해제 하는 코드가 소멸자에도 있고 Dispoase에도 있으면 코드가 중복된다. 소멸자에서도 Dispose 메소드를 호출 함으로서 리소스의 해제가 가능하다. 만약 Dispose 메소드를 이용하여  리소스를 해제 하였다면 Finalize 메스에서는 리소스를 해제 핮 않아도 된다. 이런경우 System.GC 클래스의  SuppressFinalize 메소드를 호출하여 Finalize 메소드가 호출되지 않게 설정 할 수 있다. 결국 아래의 소스와 같은 구조가 될것이다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace studycsharp
    {
        class Garbage : IDisposable
        {
            private bool isDispose = false;
            private string name;

            public Garbage(string name)
            {
                this.name = name;
                Console.WriteLine("{0} 객체 생성됨", this.name);
            }

            public void Dispose()
            {
                isDispose = true;

                Console.WriteLine("{0} 객체의 리소스 해제 ok", name);
                GC.SuppressFinalize(this);
            }

            ~Garbage()
            {
                //실제로 1, 2번째는 소멸자에 들어오지 못한다
                //SuppressFinalize 함수 때문이다.
                if(!isDispose)
                {
                    Dispose();
                }
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Garbage g1 = new Garbage("1번째");
                Garbage g2 = new Garbage("2번째");
                Garbage g3 = new Garbage("3번째");
                Garbage g4 = new Garbage("4번째");

                g1.Dispose();
                GC.SuppressFinalize(g2);

            }
        }
    }

    ======================================================================

    using System;
    using System.ComponentModel;

    class Garbage : IDisposable
    {
    private bool isDispose = false;
    private Component component = new Component();
    private string name;

    public Garbage(string name) 
    {
    this.name = name;
    Console.WriteLine("{0}객체 생성됨...", this.name);
    }

    ~Garbage() 
    {
    if (!isDispose) 
    {
    Dispose();
    }
    }

    public void Dispose() 
    {
    isDispose = true;

    Console.WriteLine("{0}객체의 리소스 해제 OK...{1}", name, component);
    component.Dispose();
    //this=null;
    component=null;
    GC.SuppressFinalize(this);
    }
    }

    class GarbageTest1 
    {
    static void Main() 
    {
    Garbage g1 = new Garbage("1번객체");
    g1.Dispose();
    GC.SuppressFinalize(g1);
    }
    }

    가비지 컬렉터는 Mark & Compact 알고리즘에 의해서 객체들의 관계를 추적한다. 

    즉, 인스턴스화 시켰던 DataSet에 null을 할당하면 DataSet은 사용하지 않는 객체로 간주되고 가비지 컬렉션시에 메모리 해제의 대상이 된다. 

    하지만 DataSet은 DataTable을 가지고 있고, DataRow, DataItem과 같은 여러 객체들을 참조하고 있다. 

    바로 DataSet이 해제가 되면 그와 상호관련이 있었던 모든 객체들 역시 메모리를 해제해야 할 것이고 바로 가비지 컬렉터는 이러한 복잡한 관계를 Mark&Compact 알고리즘을 이용해서 이해하고 각각 해제될 수 있는 것이다. 



    가비지 컬렉터는 세대별로 나누어서 메모리를 관리한다. 즉, 메모리를 관리하는 그릇이 3개가 존재한다고 보면 된다. 세대는 0,1,2 세대로 나누어지고 최초의 메모리는 무조건 0이라는 공간에서 관리가 된다고 보면 된다. 그리고 가비지를 한번 정리하였지만 해제되지 않은 객체는 바로 다음 세대로 이동하게 된다. 가비지 컬렉션은 세대별로 독립적이라고 보면 된다. 즉, 가비지 컬렉션이 일어날 때 0,1,2 세대 별로 동시에 발생하는 것이 아니라 개별적으로 가비지 컬렉션을 수행한다는 것이다. 최근의 생긴 메모리 수록 즉, 0세대일 수록 컬렉션이 많이 발생한다. 이것은 당연한 동작 원리이다. 보통 한번 사용한 객체는 꾸준히 많이 사용하지만 그렇지 않은 객체는 단기적으로 사용을 많이 하기 때문이다. 다음 [그림1]은 가비지 컬렉터가 가비지 컬렉션을 수행하는 장면을 보여주고 있다.

    그림1
    [그림1]가비지 컬렉션의 동작

    이렇게 수행을 하고 남은 객체는 바로 그 위의 세대로 승격된다. 다음 [그림2]를 살펴보자.


    그림2

    [그림2]다음 세대로 승격


    이 승격은 2세대까지 2번 승격된다. 0세대는 활발하게 가비지 컬렉션이 수행되지만 2세대는 거의 발생되지 않는다고 보면 된다




    - 코드에서 System.GC.Collect()를 이용해서 직접 가비지 컬렉션의 수행을 호출하는 것은 가급적 피한다. 

    닷넷에 연고가 있는 독자라면 가비지 컬렉션을 수동으로 수행하면 안좋다 라는 말을 많이 들어봤을 것이다. 그럼 이제 그 이유를 살펴보자. 이유는 크게 두 가지 이유가 있다. 첫 번째는 가비지 컬렉션이 수행하는 메카니즘이 생각보다 간단하지 않기 때문이므로 성능상 부하가 있을 수 있다. 왜냐하면 현재 객체가 사용 중인지 확인하는 작업이 필요하며 그 작업이 끝난 후에 객체를 파괴한다. 객체를 파괴할 때에는 참조되고 있는 객체 역시 파괴해야 한다는 것이다. 뿐만 아니라 파괴된 객체들의 빈자리를 매꾸기 위해서 가비지 컬렉터는 객체들을 재배치 작업(Compaction 작업)을 수행하게 되므로 메모리가 많을수록 그 부하가 클 수 있기 때문이다. 두 번째 이유는 객체가 승격되기 때문이다. 객체가 0세대에서 한번 승격되면 그 객체가 더 이상 사용하지 않는다 하더라도 자동적으로 그 객체가 정리될 확률이 줄어들기 때문이다. 그렇기 때문에 코드에서 직접 가비지 컬렉션의 수행의 호출을 추천하지 않는 것이다. 




    '@ 16. 1 ~ 17. 1 > C#' 카테고리의 다른 글

    as 연산자 / yield  (0) 2016.12.08
    nullable  (0) 2016.11.19
    C#AVL 트리  (0) 2016.11.18
    델리게이트, 이벤트 - 1  (0) 2016.11.18
    object 란? boxing unboxing!  (0) 2016.11.18
Designed by Tistory.