2009-11-13 3 views
28

내 업무에서 OutOfMemoryExceptions에 문제가있었습니다. 나는 어떤 행동을 모방하기위한 간단한 코드를 작성했으며, 나는 다음과 같은 수수께끼로 끝을 맺었다. 메모리가 부족할 때 폭발하는이 간단한 코드를보십시오..NET 가비지 수집기의 신비

  //... 
      for (;;iter++) 
      { 
       list.Add(new byte[10000]); 

       if (iter % 1000 == 0) 
        GC.Collect(); 
      } 
      //... 

그리고 놀라운 :

class Program 
{ 
    private static void Main() 
    { 
     List<byte[]> list = new List<byte[]>(200000); 
     int iter = 0; 

     try 
     { 
      for (;;iter++) 
      { 
       list.Add(new byte[10000]); 
      } 
     } 
     catch (OutOfMemoryException) 
     { 
      Console.WriteLine("Iterations: " + iter); 
     } 
    } 
} 

내 컴퓨터에 그것은 그럼 난 각각의 천 반복 한 후 루프에 GC.Collect 전화를 추가

Iterations: 148008

으로 돌아가 셨습니다

Iterations: 172048

각 10 회 반복 후에 GC.Collect을 호출했을 때 나는 심지어 193716 사이클을 얻었습니다.

  1. 어떻게 GC.Collect에 수동 호출 (더 할당 30 %까지)와 같은 심각한 영향을 미칠 수있다 :이 이상한 일들이 있습니까?

  2. "손실 된"참조가없는 경우 GC 목록을 수집 할 수 있습니까?

+6

흥미로운 질문입니다. 나는 그것이 메모리 공간의 조각 모음과 관련이 있다고 생각할 것이다. GC.Collect를 자주 호출하면 인접한 블록을 더 크게 유지할 수 있으므로 나중에 예외가 발생한다. 그러나 그것은 단지 야생의 추측이며 다른 사람들의 의견을 기다리고 있습니다. – Lucero

+2

나는 당신에게 질문에 답할 수는 없지만 2)에서는 "추가"방법을 조사 할 수 없기 때문에 잃어버린 참조가 없다고 말할 수 없다. – flq

+0

@ 프랭크 : 그건 당연한 얘기지만, 평범한 배열을 사용해 보았는데 그 결과는 같았습니다. – Elephantik

답변

11

가비지 수집 프로세스의 일부는 압축 단계입니다. 이 단계에서 할당 된 메모리 블록이 조각을 줄이기 위해 이동됩니다. 메모리가 할당되면 할당 된 메모리의 마지막 청크가 중단 된 직후에 항상 할당되는 것은 아닙니다. 가비지 컬렉터가 사용 가능한 공간을보다 효율적으로 사용하여 더 많은 공간을 확보하기 때문에 좀 더 쥐어 짜낼 수 있습니다.

일부 테스트를 실행하려고하지만 내 컴퓨터가 테스트를 수행 할 수 없습니다. 그들이 당신의 의견, 주변의 GC 이동 것들, 그것은 밖으로 아무것도 닦아되지 관해서는

byte[] b = new byte[10000]; 
GCHandle.Alloc(b, GCHandleType.Pinned); 
list.Add(b); 

주위에 이동되지 않도록이 사용 해보세요, 그것은 메모리의 개체를 아래로 핀에 GC을 말할 것이다 , 그것은 단지 모든 메모리 공간을 더 잘 사용하고 있습니다. 시도해 보도록하고 이것을 단순화하십시오. 처음으로 바이트 배열을 할당 할 때, 0에서 10000까지 메모리에 삽입된다고 말하십시오. 다음 번에 바이트 배열을 할당하면 10001에서 시작할 수 없습니다. 10500에서 시작할 수 있습니다. 사용되지 않는 499 바이트가 있으며 응용 프로그램에서 사용하지 않습니다. 따라서 GC이 압축을 수행하면 10500 배열을 10001로 이동하여 추가 499 바이트를 사용할 수 있습니다. 그리고 다시, 이것은 단순화 된 방법입니다.

+1

그건 말이 되겠지만, 1) 나는 아직도 어떤 물건도 볼 수 없다. (좋아, List.추가는 약간의 잡음을 추가 할 수 있지만 resharper를 사용한 빠른 검사는 그렇지 않음을 보여줍니다.); 2) 너무 많은 메모리가 할당되면 GC는 프레임 워크에 의해 여러 번 호출되어야하며 동일한 작업을 수행해야합니다. – Elephantik

+0

그게 내가 생각했던 것입니다 (질문에 대한 저의 의견을보십시오). 그러나 실제로 의미가없는 것은 GC가 불충분 한 메모리의 경우에 호출되어야하며, 따라서 그 시점에서 메모리를 압축해야한다는 것입니다. 그러나 GC는 어떻게 든 OS에서 블록 단위로 메모리를 할당하므로 대용량 메모리 블록이 하나도 없지만 처리해야 할 일련의 메모리 블록보다 많기 때문에 다를 수 있습니다. GC.COllect를 호출하면 블록을 더 재구성 할 수 있으므로 손실 된 공간 (OS 블록 끝에이 할당에 사용되지 않는 사용되지 않은 메모리)이 줄어들 수 있습니다. – Lucero

+2

나는 당신의 요지를 보았지만, 죽은 물건이없는 한, 나는 의도적 인 기억의 분열에 대한 이유를 볼 수 없다. GC의 장점 중 하나는 조각화되지 않은 사용 가능한 메모리가 있어야하므로 새로 만든 개체는 여유 공간을 찾을 필요가 없습니다. 그리고 앞서 언급했듯이 GC 자동 호출은 똑같이해야합니다. – Elephantik

5

사용중인 CLR에 따라 일부 대형 개체 힙 문제가있을 수 있습니다.

큰 블록 할당의 문제를 설명하는이 기사를 보아라. (그리고 200000 개의 항목이있는 목록은 확실히 큰 블록이고, 다른 블록은있을 수도 그렇지 않을 수도있다. 어떤 배열은 LOH에 놓이게된다. 그들은 8k를 도달하고, 다른 사람은 85k 후에).

http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

+0

좋은 점은 LOH가 관련 될 수 있지만 항상 큰 목록이 계속 앉아 있으므로 LOH가 조각화되어서는 안됩니다. – Elephantik

+0

작은 배열을 축소하여 Lucero의 포인트를 테스트합니다. 나는 그 85000 한도 만 알고있다. –

+0

LOH를 피하기 위해 더 작은 배열 배열에 삽입하는 테스트를 수행했으며 동작은 여전히 ​​동일합니다. – Elephantik

2

CLR은 때때로 LOH에 배열을 배치합니다. WinDbg을 통해 메모리 덤프를 살펴 본다면 85,000 바이트 미만의 배열이 있음을 알 수 있습니다.그것은 문서화되지 않은 행동입니다 -하지만 그것이 작동하는 방식입니다.

LOH 힙을 조각화하고 LOH 힙이 압축되지 않아 OutOfMemoryErrors가 발생합니다. 의 질문과 관련하여

:

2)에는 "손실"참조를 거기 없을 때 도대체 GC가 수집 할 수있는 것, (심지어) 목록의 용량을 미리 설정 한?

목록에 추가하기 위해 전달한 new byte[10000]에 대한 덮어 쓰기 된 참조가 있습니다. 지역 변수가 컴파일되어 new byte[10000]에 할당됩니다. 루프의 모든 반복에 대해 미리 정의 된 크기 10000의 새 바이트 []를 만들고 로컬 변수에 할당합니다. 변수에 대한 이전 값은 덮어 쓰여지고 그 변수가 살고있는 세대 (이 경우 가능하면 LOH)에 대해 GC가 다음에 실행될 때 메모리가 수집에 적합합니다.

+0

"배열의 복사본이 만들어지고 목록으로 전달 된"것은 잘못되었습니다. –

+0

이것은 "그냥 잘못"입니까? 배열의 새로운 복사본은 각 반복마다 만들어집니다 (동일한 배열 크기이므로 복사본을 의미합니다). 그런 다음 새 배열이 기존 목록 에 추가되어 각 반복마다 바이트 [10000]의 수가 증가합니다. –

+0

좋아, 이제는 "새로운 배열이 만들어졌다"는 것을 이해했다. 나에게 배열의 사본은 그 내용의 사본으로 이해되며, 그런 일은 여기서 일어나지 않습니다. –

0

.NET에서 비슷한 크기의 문제가 발생했습니다. 내 바이트 []에 임의의 크기가 있습니다.

가능하면

+0

x64 환경은 어떤 기능을 수행합니까? 예, 가상 메모리의 양은 증가하지만 OutOfMemoryException이 발생하기까지 걸리는 시간이 길어집니다. 문제의 근본 원인은 여전히 ​​해결되지 않습니다. LOH의 크기는 x86에서 x64로 변경되지 않으며 GC가 LOH를 압축하지 않기 때문에 LOH는 여전히 단편화됩니다. –

+0

답변을 수정했습니다. 네가 옳아. – NickD