수색…


소개

.Net에서 new ()로 만든 객체는 관리되는 힙에 할당됩니다. 이러한 객체는이를 사용하는 프로그램에 의해 명시 적으로 마무리되지 않습니다. 대신이 프로세스는 .Net Garbage Collector에 의해 제어됩니다.

아래 예제 중 일부는 가비지 콜렉터가 작동하는 모습과 그 동작에 대한 몇 가지 중요한 세부 사항을 보여주는 "랩 사례"와 가비지 콜렉터의 올바른 처리를 위해 클래스를 준비하는 방법에 중점을 둡니다.

비고

가비지 수집기는 할당 된 메모리 측면에서 프로그램 비용을 낮추기위한 것이지만 처리 시간 측면에서 비용이 듭니다. 전반적인 절충안을 달성하기 위해 가비지 컬렉터를 염두에두고 프로그래밍 할 때 고려해야 할 여러 가지 최적화가 있습니다.

  • Collect () 메서드를 명시 적으로 호출해야하는 경우 (실제로 어쨌든 사용하지 않아야 함) 메모리가 실제로 필요할 때만 데드 개체를 마무리하는 "optimized"모드를 사용하는 것을 고려하십시오
  • Collect () 메서드를 호출하는 대신 실제로 필요한 경우에만 메모리 컬렉션을 트리거하는 AddMemoryPressure () 및 RemoveMemoryPressure () 메서드를 사용하는 것이 좋습니다.
  • 메모리 수집은 모든 사용 불능 객체를 마무리하는 것이 보장되지 않습니다. 대신 가비지 수집기는 한 세대에서 다음 세대로 "살아남는"3 개의 세대를 관리합니다.
  • 가비지 수집기 스레드와 다른 응용 프로그램 스레드 (들) 사이의 간섭 정도가 다른 결과를 초래하는 설치 미세 조정을 포함하여 다양한 요인에 따라 여러 스레딩 모델이 적용될 수 있습니다.

(쓰레기) 수집의 기본 예

주어진 다음 클래스 :

public class FinalizableObject 
{
    public FinalizableObject()
    {
        Console.WriteLine("Instance initialized");
    }

    ~FinalizableObject()
    {
        Console.WriteLine("Instance finalized");
    }
}

인스턴스를 생성하지 않고 그것을 생성하는 프로그램 :

new FinalizableObject(); // Object instantiated, ready to be used

다음 출력을 생성합니다.

<namespace>.FinalizableObject initialized

다른 일이 발생하지 않으면 프로그램이 끝날 때까지 개체가 완료되지 않습니다.이 프로세스는 관리되는 힙의 모든 개체를 해제하여 프로세스에서 종료합니다.

다음과 같이 주어진 지점에서 가비지 수집기를 강제 실행하는 것이 가능합니다.

new FinalizableObject(); // Object instantiated, ready to be used
GC.Collect();

다음 결과가 생성됩니다.

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

이번에는 가비지 수집기가 호출 되 자마자 사용되지 않은 (일명 "죽은") 개체가 완성되어 관리되는 힙에서 해제되었습니다.

라이브 오브젝트 및 죽은 오브젝트 - 기본 사항

엄지 손가락 규칙 : 가비지 콜렉션이 발생하면 "라이브 오브젝트"는 아직 사용중인 오브젝트이고, "사용 불능 오브젝트"는 더 이상 사용되지 않는 오브젝트입니다 (콜렉션이 발생하기 전에 범위를 벗어난 변수 또는 필드를 참조하는 경우) .

다음 예제에서 (편의상 FinalizableObject1과 FinalizableObject2는 위의 예제에서 FinalizableObject의 하위 클래스이므로 초기화 / 종료 메시지 동작을 상속받습니다.)

var obj1 = new FinalizableObject1(); // Finalizable1 instance allocated here
var obj2 = new FinalizableObject2(); // Finalizable2 instance allocated here
obj1 = null; // No more references to the Finalizable1 instance 
GC.Collect();

출력은 다음과 같습니다.

<namespace>.FinalizableObject1 initialized
<namespace>.FinalizableObject2 initialized
<namespace>.FinalizableObject1 finalized

가비지 컬렉터가 호출 될 때 FinalizableObject1은 죽은 객체이고 Finalize되는 반면 FinalizableObject2는 활성 객체이며 관리되는 힙에 보관됩니다.

여러 죽은 개체

두 개 (또는 여러 개의) 죽은 객체가 서로를 참조하면 어떻게 될까요? OtherObject가 FinalizableObject의 public 속성이라고 가정하면 아래 예제에 표시됩니다.

var obj1 = new FinalizableObject1(); 
var obj2 = new FinalizableObject2();
obj1.OtherObject = obj2;
obj2.OtherObject = obj1;
obj1 = null; // Program no longer references Finalizable1 instance
obj2 = null; // Program no longer references Finalizable2 instance
// But the two objects still reference each other
GC.Collect();

그러면 다음과 같은 결과가 출력됩니다.

<namespace>.FinalizedObject1 initialized
<namespace>.FinalizedObject2 initialized
<namespace>.FinalizedObject1 finalized
<namespace>.FinalizedObject2 finalized

두 객체는 ​​서로 참조되지만 (실제로 살아있는 객체에서 다른 객체에 대한 참조가 없기 때문에) 관리 힙에서 마무리되고 해제됩니다.

약한 참조

약한 참조는 다른 객체 ( "대상"이라고도 함)에 대한 참조이지만 해당 객체가 가비지 수집되지 않도록하기 때문에 "약한"참조입니다. 즉, 가비지 콜렉터가 오브젝트를 "라이브"또는 "사용 불가능"으로 평가할 때 약한 참조는 계산되지 않습니다.

다음 코드는 :

var weak = new WeakReference<FinalizableObject>(new FinalizableObject());
GC.Collect();

출력을 생성합니다.

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

개체는 WeakReference 변수 (가비지 수집기가 호출되었을 때 여전히 범위 내에 있음)에 의해 참조 되더라도 관리되는 힙에서 해제됩니다.

결과 # 1 : 언제든지 WeakReference 대상이 관리 힙에 여전히 할당되어 있는지 여부를 추정하는 것은 안전하지 않습니다.

결과 # 2 : 프로그램이 Weakreference의 대상에 액세스해야 할 때마다 코드가 할당 된 상태의 두 가지 경우 모두에 대해 제공되어야합니다. 대상에 액세스하는 방법은 TryGetTarget입니다.

var target = new object(); // Any object will do as target
var weak = new WeakReference<object>(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if(weak.TryGetTarget(out target))
{
    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

WeakReference의 일반 버전은 .Net 4.5부터 사용할 수 있습니다. 모든 프레임 워크 버전은 동일한 방식으로 빌드되고 다음과 같이 검사되는 비 제네 타입, 유형없는 버전을 제공합니다.

var target = new object(); // Any object will do as target
var weak = new WeakReference(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if (weak.IsAlive)
{
    target = weak.Target;

    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

마무리 자 ()와 종료 자의 비교

개체가 더 이상 사용되지 않는 즉시 메모리가 많은 리소스가 해제되는 것을 보장하기 위해 Dispose () 메서드를 구현하고 포함하는 클래스를 IDisposable로 선언하십시오. "catch"는 Dispose () 메서드가 호출 될 것이라는 강력한 보장이 없다는 것입니다 (개체의 수명이 다할 때 항상 호출되는 finalizer와는 다릅니다).

하나의 시나리오는 명시 적으로 생성 한 객체에 대해 Dispose ()를 호출하는 프로그램입니다.

private void SomeFunction()
{
    // Initialize an object that uses heavy external resources
    var disposableObject = new ClassThatImplementsIDisposable();

    // ... Use that object

    // Dispose as soon as no longer used
    disposableObject.Dispose();

    // ... Do other stuff 

    // The disposableObject variable gets out of scope here
    // The object will be finalized later on (no guarantee when)
    // But it no longer holds to the heavy external resource after it was disposed
}

또 다른 시나리오는 클래스가 프레임 워크에 의해 인스턴스화되도록 선언하는 것입니다. 이 경우 새 클래스는 일반적으로 기본 클래스를 상속합니다. 예를 들어 MVC에서는 System.Web.Mvc.ControllerBase의 하위 클래스로 컨트롤러 클래스를 만듭니다. 기본 클래스가 IDisposable 인터페이스를 구현할 때 이것은 Dispose ()가 프레임 워크에 의해 올바르게 호출된다는 좋은 힌트입니다. 그러나 다시 강한 보증은 없습니다.

따라서 Dispose ()는 종료자를 대신 할 수 없습니다. 대신 두 가지 목적에 따라 사용해야합니다.

  • finalizer는 궁극적으로 리소스를 해제하여 메모리 누수가 발생하지 않도록합니다.
  • Dispose ()는 전체 메모리 할당에 대한 부담을 덜어주기 위해 리소스가 더 이상 필요하지 않게되는 즉시 같은 리소스를 해제합니다.

물체의 올바른 처분 및 마무리

Dispose ()와 finalizer는 다른 목적을 목표로하므로 외부 메모리가 많은 외부 리소스를 관리하는 클래스는이 두 가지를 모두 구현해야합니다. 그 결과 두 가지 가능한 시나리오를 잘 처리 할 수 ​​있도록 클래스를 작성합니다.

  • 파이널 라이저 만 호출하면
  • Dispose ()가 먼저 호출되고 나중에 finalizer가 호출 될 때

한 가지 해결책은 한 번 또는 두 번 실행하면 한 번만 실행하는 것과 같은 결과를 생성하는 방식으로 정리 코드를 작성하는 것입니다. 실행 가능성은 예를 들어 다음과 같이 정리의 특성에 따라 다릅니다.

  • 이미 닫힌 데이터베이스 연결을 닫으면 아무 효과가 없으므로 작동합니다.
  • 일부 "사용 횟수"를 업데이트하는 것은 위험하며 한 번이 아닌 두 번 호출 될 때 잘못된 결과를 생성합니다.

보다 안전한 솔루션은 설계에 따라 외부 컨텍스트가 무엇이든간에 정리 코드가 한 번만 호출된다는 것입니다. 전용 플래그를 사용하여 "고전적인 방법"을 달성 할 수 있습니다.

public class DisposableFinalizable1: IDisposable
{
    private bool disposed = false;

    ~DisposableFinalizable1() { Cleanup(); }

    public void Dispose() { Cleanup(); }

    private void Cleanup()
    {
        if(!disposed)
        {
            // Actual code to release resources gets here, then
            disposed = true;
        }
    }
}

또는 Garbage Collector는 Dispose가 호출 된 후 Finalizer를 건너 뛰는 특정 메서드 SuppressFinalize ()를 제공합니다.

public class DisposableFinalizable2 : IDisposable
{
    ~DisposableFinalizable2() { Cleanup(); }

    public void Dispose()
    {
        Cleanup();
        GC.SuppressFinalize(this);
    }

    private void Cleanup()
    {
        // Actual code to release resources gets here
    }
}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow