2013-04-18 4 views
10

.NET에서 유형이 가비지 수집되지 않는다는 것이 잘 알려져 있습니다. 즉, 유형을 가비지 수집하지 않아도됩니다. 즉, f.ex로 재생하는 경우입니다. Reflection.Emit, AppDomains 등을 언로드 할 때 조심해야한다. 적어도 물건이 어떻게 작동 하는지를 이해하는 방법이다.MakeGenericType/제네릭 형식이 가비지 수집됩니까?

일반 유형 가비지 수집 된 것인지 궁금합니다. 정확히 말하면 MakeGenericType으로 생성 된 제네릭입니다. 예를 들어 사용자 입력을 기반으로합니다.

public interface IRecursiveClass 
{ 
    int Calculate(); 
} 

public class RecursiveClass1<T> : IRecursiveClass 
            where T : IRecursiveClass,new() 
{ 
    public int Calculate() 
    { 
     return new T().Calculate() + 1; 
    } 
} 
public class RecursiveClass2<T> : IRecursiveClass 
            where T : IRecursiveClass,new() 
{ 
    public int Calculate() 
    { 
     return new T().Calculate() + 2; 
    } 
} 

public class TailClass : IRecursiveClass 
{ 
    public int Calculate() 
    { 
     return 0; 
    } 
} 

class RecursiveGenericsTest 
{ 
    public static int CalculateFromUserInput(string str) 
    { 
     Type tail = typeof(TailClass); 
     foreach (char c in str) 
     { 
      if (c == 0) 
      { 
       tail = typeof(RecursiveClass1<>).MakeGenericType(tail); 
      } 
      else 
      { 
       tail = typeof(RecursiveClass2<>).MakeGenericType(tail); 
      } 
     } 
     IRecursiveClass cl = (IRecursiveClass)Activator.CreateInstance(tail); 
     return cl.Calculate(); 
    } 

    static long MemoryUsage 
    { 
     get 
     { 
      GC.Collect(GC.MaxGeneration); 
      GC.WaitForFullGCComplete(); 
      return GC.GetTotalMemory(true); 
     } 
    } 

    static void Main(string[] args) 
    { 
     long start = MemoryUsage; 

     int total = 0; 
     for (int i = 0; i < 1000000; ++i) 
     { 
      StringBuilder sb = new StringBuilder(); 
      int j = i; 
      for (int k = 0; k < 20; ++k) // fix the recursion depth 
      { 
       if ((j & 1) == 1) 
       { 
        sb.Append('1'); 
       } 
       else 
       { 
        sb.Append('0'); 
       } 
       j >>= 1; 
      } 

      total += CalculateFromUserInput(sb.ToString()); 

      if ((i % 10000) == 0) 
      { 
       Console.WriteLine("Current memory usage @ {0}: {1}", 
            i, MemoryUsage - start); 
      } 
     } 

     Console.WriteLine("Done and the total is {0}", total); 
     Console.WriteLine("Current memory usage: {0}", MemoryUsage - start); 

     Console.ReadLine(); 
    } 
} 

당신이 재귀의 끝을 표시하는 '꼬리'클래스, 일반 유형이 정의 '가능성 재귀'되어 볼 수 있듯이 : :-)

그래서 나는 다음 테스트 케이스를 구축 . GC.TotalMemoryUsage이 속임수가되지 않도록 작업 관리자를 열었습니다.

지금까지 그렇게 좋았습니다. 내가 한 다음 일은이 짐승을 태우고 '추억'을 기다리는 동안 ... 나는 내 기대와 달리 - 아니요, 시간이 지남에 따라 더 많은 메모리를 소비하는 것으로 나타났습니다. 사실, 시간이 지남에 따라 메모리 소비가 약간 감소합니다.

누군가 설명해 주시겠습니까? 일반 유형은 실제로 GC에서 수집합니까? 그리고 만약에 그렇다면 ... 반사도 있습니다. 쓰레기 수거 사례가 있습니까?

+0

는 다음 확인. –

+0

아마도 이것이 [JIT 컴파일러로 제네릭을 컴파일하는 방법은 무엇입니까?] (http://stackoverflow.com/questions/5342345/how-do-generics-get-compiled-by-the-jit-compiler) –

답변

19

첫 번째 질문에 대한 답변 :

유형의 일반적인 구조는 수집되지 않습니다. 당신이 C<string>C<object> 구성하는 경우

그러나 CLR은 실제로 한 번만 방법에 대한 코드를 생성; 문자열에 대한 참조와 객체에 대한 참조가 같은 크기로 보장되므로 안전하게 수행 할 수 있습니다. 그것은 꽤 영리하다. C<int>C<double>을 구성하면 메소드의 코드가 각 구성에 대해 두 번 생성됩니다. (방법에 대한 코드는 물론 전혀 발생된다고 가정하면, 방법은 수요에 jitted되며, 그 이유는라는 jitting.)

일반적인 종류가 수집되지 않은 보여주기 위해 대신 제네릭 형식을 만들

class C<T> { public static readonly T Big = new T[10000]; } 

C<object>C<string>은 메서드에 대해 생성 된 코드를 공유하지만 각 코드는 자체 정적 필드를 가져 오며 해당 필드는 영원히 존재합니다. 더 많은 유형을 만들수록 더 많은 메모리가 큰 배열로 채워집니다.

이제 이러한 유형을 수집 할 수없는 이유를 알 수 있습니다. 우리는 누군가가 미래에 언제든지 배열 중 하나의 멤버에 액세스하려고하는지 알 수있는 방법이 없습니다. 마지막 배열 액세스가 언제 이루어질 지 모르기 때문에 영원히 살아야합니다. 따라서이를 포함하는 유형도 영원히 살아야합니다.


두 번째 질문에 대답하려면 : 동적으로 방출되는 어셈블리를 수집하는 방법이 있습니까?

예.문서는 여기에 있습니다 :은`Target`는 GC 패스를 실행 한 후 null의 경우

http://msdn.microsoft.com/en-us/library/dd554932.aspx

당신은 당신의 생성 된 제네릭 형식에`WeakReference`를 작성하여 실험 할 수
+0

@ ErikLippert 와우, 대단한 대답이다. Eric, 고마워! 나는 정적에 관한 것을 생각했다. 그래서 나는 클래스에 정적을 추가하지 않았다. 나는 여전히이 라인에 대해서 생각하고있다. '실제로 메소드에 대한 코드를 한 번만 생성한다. [..]'. 그렇다면 정적이없는 참조 유형 제네릭 클래스가 두 개 있으면 내부적으로 하나의 '유형'만이 메모리에 존재한다는 것입니까? (예를 들어 유형이 단지 코드이고 한번만 생성 될 메모리가 없기 때문에) 그러나 이는 또한 JIT 최적화 경계를 의미합니다 (T가 Foo와 Bar 중 하나의 코드 인스턴스 만 있으면 인라이닝이 불가능합니다). – atlaste

+0

'string GetTypeName (T param) {return typeof (T) .ToString();}'과 같은 메소드의 코드 사본 하나가 모든 클래스 유형'T'에 대해 공유된다면, 코드는 어떻게 불렀다 고? 어떻게 든 숨겨진 매개 변수로 전달됩니까? – supercat

+3

@supercat : Magic! –

관련 문제