2010-04-27 2 views
18

AppDomain.Unload (myDomain)을 수행 할 때도 전체 가비지 수집을 수행 할 것으로 예상됩니다. 의 제프리 리히터에 따르면AppDomain.Unload를 호출해도 가비지 수집이 발생하지 않는 이유는 무엇입니까?

그가의 AppDomain.Unload 중에 있다고 말한다 "CLR C#을 통해"에 의해 생성 된 모든 객체 사용하는 메모리를 회수,

는 CLR이 발생하는 가비지 수집을 강제로 이제 언로드 된 AppDomain. 에 대한 Finalize 메서드가 호출되어 개체에서 올바르게 정리할 수 있습니다. "사용자 정의 .NET Framework 공용 언어 런타임"에서 "스티븐 Pratschner"에 따르면

: 모든 파이 나라 실행 한 더 이상 스레드가 도메인에서 실행되지 않습니다 후

는 CLR이 언로드 할 준비가 내부 구현에 사용 된 모든 메모리 내 데이터 구조 그러나 이렇게되기 전에 도메인에있는 개체를 수집해야합니다. 다음 가비지 수집이 발생하면 응용 프로그램 도메인 데이터 구조가 프로세스 주소 공간에서 언로드되고 도메인은 언로드 된 것으로 간주됩니다.

나는 그들의 말을 잘못 해석합니까? 나는 (.NET 2.0 SP2의) 예상치 못한 동작을 재현 할 수있는 다음과 같은 솔루션을했다 :

이 인터페이스를 포함하는 "인터페이스"라 불리는 클래스 라이브러리 프로젝트 :

public interface IXmlClass 
    { 
     void AllocateMemory(int size); 

     void Collect(); 
    } 

클래스 라이브러리 프로젝트라는 "으로 ClassLibrary1을" 이는 "인터페이스"를 참조하고이 클래스가 포함되어 다음과 같은 논리를 "인터페이스"프로젝트를 참조하고 수행

public class XmlClass : MarshalByRefObject, IXmlClass 
{ 

    private byte[] b; 

    public void AllocateMemory(int size) 
    { 
     this.b = new byte[size]; 
    } 

    public void Collect() 
    { 
     Console.WriteLine("Call explicit GC.Collect() in " + AppDomain.CurrentDomain.FriendlyName + " Collect() method"); 
     GC.Collect(); 
     Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); 
    } 

    ~XmlClass() 
    { 
     Console.WriteLine("Finalizing in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName); 
    } 
} 

콘솔 응용 프로그램 프로젝트 :

를 691,363,210
static void Main(string[] args) 
{ 
    AssemblyName an = AssemblyName.GetAssemblyName("ClassLibrary1.dll"); 
    AppDomain appDomain2 = AppDomain.CreateDomain("MyDomain", null, AppDomain.CurrentDomain.SetupInformation); 
    IXmlClass c1 = (IXmlClass)appDomain2.CreateInstanceAndUnwrap(an.FullName, "ClassLibrary1.XmlClass"); 
    Console.WriteLine("Loaded Domain {0}", appDomain2.FriendlyName); 
    int tenmb = 1024 * 10000; 
    c1.AllocateMemory(tenmb); 
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); 
    c1.Collect(); 
    Console.WriteLine("Unloaded Domain{0}", appDomain2.FriendlyName); 
    AppDomain.Unload(appDomain2); 
    Console.WriteLine("Number of collections after unloading appdomain: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); 
    Console.WriteLine("Perform explicit GC.Collect() in Default Domain"); 
    GC.Collect(); 
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); 
    Console.ReadKey(); 
} 

콘솔 응용 프로그램을 실행하는 출력은 다음과 같습니다

Loaded Domain MyDomain 
Number of collections: Gen0:0 Gen1:0 Gen2:0 
Call explicit GC.Collect() in MyDomain Collect() method 
Number of collections: Gen0:1 Gen1:1 Gen2:1 
Unloaded Domain MyDomain 
Finalizing in AppDomain MyDomain 
Number of collections after unloading appdomain: Gen0:1 Gen1:1 Gen2:1 
Perform explicit GC.Collect() in Default Domain 
Number of collections: Gen0:2 Gen1:2 Gen2:2 

것들 통지 :

  1. 쓰레기 수집이 프로세스 당 이루어집니다 (다만 재교육)

  2. 객체 언로드 된 appdomain에서 finalizer가 호출되었지만 가비지 수집이 수행되지 않았습니다.) AllocateMemory (의해 만들어진 10 메가 목적은 (상기 실시 예에서 명시하는 GC.Collect()를 수행 한 후 수집되거나 가비지 수집기가 언젠가 나중에

기타 참고한다면 :. 그것이 케이 . 'XmlClass가 마무리가인지 아닌지 t 정말 문제는 같은 문제가 위의 예에서 발생

질문 :.

  1. 왜의 AppDomain.Unload는 가비지 콜렉션 발생하지 않습니다 부르심 않습니다를?해당 호출 결과를 가비지 수집으로 만들 수있는 방법이 있습니까?

  2. Inside AllocateMemory() LargeObject 힙을 가져오고 생성 2 개체가 될 짧은 수명의 큰 xml 문서 (16MB 이하)를로드 할 계획입니다. 명시적인 GC.Collect() 또는 가비지 수집기의 명시 적 프로그래밍 방식 컨트롤의 다른 종류를 사용하지 않고 메모리를 수집 할 수있는 방법이 있습니까?

답변

17

추가 정보 : 친절하게 질문을 살펴 것이 었습니다 제프리 리히터 일부 메일 교환 후

:

OK, 나는 귀하의 게시물을 참조하십시오.
먼저 XMLClass 객체가 GC 될 때까지 배열이 GC되지 않으며 Finalize 메소드가 포함되어 있으므로이 객체를 수집하는 데 두 개의 GC가 필요합니다.
두 번째로 appdomain을 언로드하면 적어도 도달 할 수있는 개체를 확인하여 Finalize 메서드를 호출 할 수있는 유일한 방법이므로 GC의 마킹 단계를 수행해야합니다.
그러나 GC를 압축 해제 할 때 GC의 압축 된 부분이 수행되거나 수행되지 않을 수 있습니다. 명백한 GC.CollectionCount를 호출해도 전체 내용을 알 수 없습니다. GC 마킹 단계가 발생했음을 보여주지 않습니다.
그리고 AppDomain.Unload가 콜렉션 카운트 변수를 증가시키지 않는 내부 코드를 통해 GC를 시작할 수도 있습니다. 우리는 이미 마킹 단계가 수행 중이며 수집 회수가이를 반영하지 않는다는 사실을 이미 알고 있습니다.

더 나은 테스트는 디버거의 일부 개체 주소를보고 압축이 실제로 발생하는지 확인하는 것입니다. 그것이 (그리고 나는 그것이 의심되는 경우), 다음 컬렉션 수를 정확하게 업데이 트되지 않습니다.

이 답변을 웹 사이트에 게시하려면 다음을 수행하십시오. 의 AppDomain.Unload 전에

:

그의 조언을 복용하고 SOS를 조사 후에이 밝혀 (또한 종료자를 제거) 같은 주소 (의 AppDomain.Unload 후

!EEHeap -gc 
Number of GC Heaps: 1 
generation 0 starts at 0x0180b1f0 
generation 1 starts at 0x017d100c 
generation 2 starts at 0x017d1000 
ephemeral segment allocation context: none 
segment begin allocated  size 
017d0000 017d1000 01811ff4 0x00040ff4(266228) 
Large object heap starts at 0x027d1000 
segment begin allocated  size 
027d0000 027d1000 02f75470 0x007a4470(8012912) 
Total Size 0x7e5464(8279140) 
------------------------------ 
GC Heap Size 0x7e5464(8279140) 

을 더 힙 없다 압축 완료)

!EEHeap -gc 
Number of GC Heaps: 1 
generation 0 starts at 0x0180b1f0 
generation 1 starts at 0x017d100c 
generation 2 starts at 0x017d1000 
ephemeral segment allocation context: none 
segment begin allocated  size 
017d0000 017d1000 01811ff4 0x00040ff4(266228) 
Large object heap starts at 0x027d1000 
segment begin allocated  size 
027d0000 027d1000 02f75470 0x007a4470(8012912) 
Total Size 0x7e5464(8279140) 
------------------------------ 
GC Heap Size 0x7e5464(8279140) 

GC.Collect() 이후에 힙 압축이 수행되었음을 나타내는 주소가 다릅니다.

!EEHeap -gc 
Number of GC Heaps: 1 
generation 0 starts at 0x01811234 
generation 1 starts at 0x0180b1f0 
generation 2 starts at 0x017d1000 
ephemeral segment allocation context: none 
segment begin allocated  size 
017d0000 017d1000 01811ff4 0x00040ff4(266228) 
Large object heap starts at 0x027d1000 
segment begin allocated  size 
027d0000 027d1000 027d3240 0x00002240(8768) 
Total Size 0x43234(274996) 
------------------------------ 
GC Heap Size 0x43234(274996) 

더 SOS 후에 내가 도달 한 결론은 디자인에 의해 확실하고, 그 힙 압축이 반드시 수행되지 않는 것입니다. AppDomain을 언로드하는 동안 실제로 확인할 수있는 유일한 방법은 개체가 도달 할 수없는 것으로 표시되어 다음 가비지 수집 중에 수집된다는 것입니다 (응용 프로그램 도메인을 언로드 할 때 정확히 완료되지 않았습니다. 우연의 일치).

EDIT : 또한 GC 팀에서 직접 일하는 Maoni Stephens에게 질문했습니다. 의견 here에서 어딘가에 그녀의 답변을 읽을 수 있습니다. 그녀는 그것이 설계에 의한 것이라고 확인합니다. 케이스가 닫힙니다.

5
  1. 디자인에 의해 아마

    하지만이 동작 (명시 적 GC.Collect를)을 원하는 이유를 이해하지 않습니다. 파이널 라이저가 호출되는 한 객체는 파이널 라이저 큐에서 제거되고 필요한 경우 가비지 수집 될 준비가됩니다 (필요하면 gc 스레드가 실행됩니다).

  2. 일부 비싸지 않은 관리되지 않는 할당과 무거운 interop을 사용하거나 관리되지 않는 C++로 코딩 한 다음 관리되는 래퍼를 사용하여 C#을 통해 액세스 할 수 있지만 관리되는 .Net 세상에서 머무르는 한 .

    가비지 수집기의 역할을 수행하는 데 집중하는 대신 아키텍처를 다시 살펴 보는 것이 좋습니다.

+0

전적으로 동의합니다. – Steven

+0

아마도 의도적으로 질문에 추가 된 내 노트를 참조하십시오. –