C #에서 가비지 수집을 강제하는 모범 사례


118

내 경험상 대부분의 사람들은 가비지 수집을 강제하는 것이 현명하지 않다고 말할 것 같지만, 0 세대에서 항상 수집되지는 않지만 메모리가 문제가되는 큰 개체로 작업하는 경우에는 수집을 강제해도 괜찮습니까? 그렇게하기위한 모범 사례가 있습니까?

답변:


112

가장 좋은 방법은 가비지 수집을 강제하지 않는 것입니다.

MSDN에 따르면 :

"Collect를 호출하여 가비지 수집을 강제 할 수 있지만 성능 문제가 발생할 수 있으므로 대부분 피해야합니다."

그러나 Collect () 호출이 부정적인 영향을 미치지 않는지 확인하기 위해 코드를 안정적으로 테스트 할 수 있다면 계속 진행하십시오.

더 이상 필요하지 않을 때 개체가 정리되었는지 확인하십시오. 사용자 정의 개체가있는 경우 "using 문"및 IDisposable 인터페이스 사용을 살펴보십시오.

이 링크에는 메모리 / 가비지 수집 등을 해제하는 것과 관련하여 실용적인 조언이 있습니다.

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



4
개체가 관리되지 않는 메모리를 가리키는 경우 GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… )를 통해 가비지 수집기에 알릴 수 있습니다 . 이렇게하면 수집 알고리즘을 방해하지 않고 가비지 수집기에 시스템에 대한 더 많은 정보를 제공합니다.
Govert

+1 : * "using statement"및 IDisposable 인터페이스 사용을 살펴보십시오. * 마지막 수단 인 경우를 제외하고는 강제를 고려하지도 않습니다. 좋은 조언 ( '면책 조항'으로 읽음). 그러나 나는 백엔드 작업에서 활성 참조 손실을 시뮬레이션하기 위해 단위 테스트에서 수집을 강제하고 있으며 궁극적으로 TargetOfInvocationNullException.
IAbstract

33

이런 식으로보세요. 음식물 쓰레기통이 10 % 일 때 버리는 것이 더 효율적입니까, 아니면 버리기 전에 가득 채우는 것이 더 효율적입니까?

가득 채우지 않으면 외부 쓰레기통을 오가는 시간을 낭비하게됩니다. 이는 GC 스레드가 실행될 때 발생하는 것과 유사합니다. 모든 관리 스레드는 실행되는 동안 일시 중지됩니다. 그리고 내가 착각하지 않으면 GC 스레드를 여러 AppDomain간에 공유 할 수 있으므로 가비지 컬렉션은 모두에 영향을줍니다.

물론, 휴가를 떠날 예정이라면 곧 쓰레기통에 아무것도 추가하지 않을 상황에 처할 수도 있습니다. 그러면 외출하기 전에 쓰레기를 버리는 것이 좋습니다.

이것은 GC를 강제하는 것이 도움이 될 수있는 한 번일 수 있습니다. 프로그램이 유휴 상태이면 할당이 없기 때문에 사용중인 메모리가 가비지 수집되지 않습니다.


5
1 분 이상 방치하면 죽을 아기가 있고 쓰레기를 처리 할 시간이 1 분 밖에 없었다면 한꺼번에하는 대신 매번 조금씩하는 것이 좋습니다. 안타깝게도 GC :: Collect () 메서드는 자주 호출할수록 빠르지 않습니다. 따라서 실시간 엔진의 경우 처리 메커니즘을 사용하고 GC가 데이터를 풀링하도록 할 수 없다면 관리 시스템을 사용해서는 안됩니다.
Jin

1
제 경우에는 성능 조정을 위해 A * (최단 경로) 알고리즘을 반복적으로 실행하고 있습니다. 프로덕션에서는 한 번만 실행됩니다 (각 "맵"에서). 그래서 나는 GC가 각 "맵"을 탐색 한 후 강제로 강제 될 수있는 생산 상황을 더 밀접하게 모델링한다고 느끼기 때문에 "성능 측정 블록"밖에서 매 반복 전에 GC를 수행하기를 원합니다.
corlettk

측정 된 블록 이전에 GC를 호출하는 것은 실제로 생산 상황을 모델링하지 않습니다. 생산시 GC가 예측할 수없는 시간에 수행되기 때문입니다. 이를 완화하려면 여러 GC 실행을 포함하는 긴 측정을 수행하고 GC 중 피크를 분석 및 통계에 반영해야합니다.
Ran

32

가장 좋은 방법은 대부분의 경우 가비지 수집을 강제하지 않는 것입니다. (내가 작업 한 모든 시스템에 가비지 수집을 강제하고 해결하면 가비지 수집을 강제 할 필요가 없어지고 시스템 속도가 크게 빨라지는 밑줄 문제가있었습니다.)

가비지 수집기보다 메모리 사용량에 대해 더 많이 알고 있는 몇 가지 경우있습니다 . 이는 다중 사용자 응용 프로그램이나 한 번에 하나 이상의 요청에 응답하는 서비스에서는 해당되지 않을 수 있습니다.

그러나 일부 배치 유형 처리 에서는 GC보다 더 많이 알고 있습니다. 예를 들어 응용 프로그램을 고려하십시오.

  • 명령 줄에 파일 이름 목록이 제공됩니다.
  • 단일 파일을 처리 한 다음 결과 파일에 결과를 씁니다.
  • 파일을 처리하는 동안 파일 처리가 완료 될 때까지 수집 할 수없는 많은 상호 연결된 개체를 생성합니다 (예 : 구문 분석 트리).
  • 처리 한 파일간에 많은 상태를 유지하지 않습니다 .

당신은 할 수 있습니다 각 파일을 처리 한 후에는 전체 가비지 컬렉션을 강제해야 테스트 (주의 후) 케이스를 만들 수.

또 다른 경우는 일부 항목을 처리하기 위해 몇 분마다 깨어나고 수면 상태를 유지하지 않는 서비스입니다 . 그런 다음 잠들기 직전에 전체 수집을 강제하는 것이 가치 있을 있습니다.

컬렉션 강제를 고려하는 유일한 경우는 최근에 많은 개체가 생성되었고 현재 참조되는 개체가 거의 없다는 것을 알고있을 때입니다.

GC를 강제하지 않고도 이러한 유형에 대한 힌트를 줄 수있을 때 가비지 수집 API를 사용하는 것이 좋습니다.

" Rico Mariani의 Performance Tidbits " 도 참조하십시오.


2
비유 : Playschool (시스템)은 크레용 (자원)을 보관합니다. 아이들의 수 (과제)와 색의 부족에 따라 교사 (.Net)는 아이들 사이에서 할당하고 공유하는 방법을 결정합니다. 희귀 한 색상이 요청되면 교사는 수영장에서 할당하거나 사용되지 않는 색상을 찾을 수 있습니다. 교사는 사용하지 않는 크레용 (쓰레기 수거)을 주기적으로 수집하여 깔끔하게 정리할 수 있습니다 (자원 사용 최적화). 일반적으로 학부모 (프로그래머)는 최고의 교실 크레용 정리 정책을 미리 결정할 수 없습니다. 한 어린이의 예정된 낮잠은 다른 어린이의 색상을 방해하는 좋은 순간이 아닐 것입니다.
AlanK

1
@AlanK, 나는 아이들이 하루 동안 집에 갈 때, 교사 도우미가 아이들이 방해받지 않고 잘 정리할 수있는 아주 좋은 시간이기 때문에 이것을 좋아합니다. (난 그냥 같은 시간에 서비스 프로세스를 다시 시작한에서 일한 최근 시스템은 GC를 forceing의 instread.)
이안 Ringrose

21

Rico Mariani 가 제시 한 예가 좋았다고 생각합니다 . 애플리케이션 상태에 큰 변화가있는 경우 GC를 트리거하는 것이 적절할 수 있습니다. 예를 들어 문서 편집기에서 문서가 닫힐 때 GC를 트리거해도 괜찮을 수 있습니다.


2
또는 실패 이력을 보여주고 세부 수준을 높이기위한 효율적인 해결 방법을 제공하지 않는 큰 연속 개체를 열기 직전입니다.
crokusek

17

절대적인 프로그래밍에 대한 일반적인 지침은 거의 없습니다. 절반의 시간 동안 누군가가 '당신이 잘못하고있다'고 말할 때, 그들은 단지 일정량의 교리를 내뿜는 것입니다. C에서는 자체 수정 코드 또는 스레드와 같은 것에 대한 두려움이 있었으며 GC 언어에서는 GC를 강제하거나 GC가 실행되지 않도록합니다.

대부분의 지침과 좋은 경험 법칙 (그리고 좋은 디자인 관행)의 경우처럼, 확립 된 표준을 우회하는 것이 합리적 일 경우는 드뭅니다. 당신은 당신이 사건을 이해하고, 당신의 사건이 정말로 일반적인 관행의 폐지를 요구하고, 당신이 유발할 수있는 위험과 부작용을 이해하고 있음을 확신해야합니다. 그러나 그러한 경우가 있습니다.

프로그래밍 문제는 매우 다양하며 유연한 접근 방식이 필요합니다. 가비지 수집 언어에서 GC를 차단하는 것이 합리적이며 자연스럽게 발생하기를 기다리지 않고 트리거하는 것이 합당한 경우를 보았습니다. 95 %의 경우, 이들 중 하나는 문제에 제대로 접근하지 못했다는 신호가 될 것입니다. 그러나 20 명 중 1 번, 아마도 그것을위한 유효한 사례가있을 것입니다.


12

나는 가비지 수집을 능가하려고하지 않는 법을 배웠다. 즉, using파일 I / O 또는 데이터베이스 연결과 같은 관리되지 않는 리소스를 다룰 때 키워드 사용을 고수 합니다.


27
컴파일러? 컴파일러는 GC와 어떤 관련이 있습니까? :)
KristoferA

1
아무것도, 단지 코드를 컴파일하고 최적화합니다. 그리고 이것은 분명히 CLR ... 또는 .NET과 관련이 없습니다.
Kon

1
매우 큰 이미지와 같이 메모리를 많이 차지하는 객체는 명시 적으로 가비지 수집하지 않는 한 가비지 수집되지 않을 수 있습니다. 나는 이것이 (큰 물체)가 OP의 문제라고 생각합니다.
code4life

1
using으로 래핑하면 사용 범위를 벗어난 GC에 대해 예약됩니다. 컴퓨터가 폭발하지 않는 한 해당 메모리는 대부분 정리됩니다.
Kon

9

이것이 모범 사례인지 확실하지 않지만 루프에서 많은 양의 이미지로 작업 할 때 (예 : 많은 그래픽 / 이미지 / 비트 맵 개체를 만들고 처리) 정기적으로 GC.Collect를 사용합니다.

나는 GC가 프로그램이 (대부분) 유휴 상태 일 때만 실행되고 집중적 인 루프의 중간에 있지 않을 때만 실행된다는 것을 읽은 것 같아서 수동 GC가 의미가있는 영역처럼 보일 수 있습니다.


이게 꼭 필요한가요? GC 코드가 유휴 상태가 아니더라도 메모리가 필요한 경우 수집합니다.
Konrad Rudolph

지금은 .net 3.5 SP1에 있는지 확실하지 않지만 이전에는 (1.1 및 2.0에 대해 테스트했다고 생각합니다) 메모리 사용량에 차이를 만들었습니다. 물론 GC는 필요할 때 항상 수집하지만 20 개만 필요할 때도 100 메가의 RAM을 낭비하게 될 수도 있습니다.하지만 몇 가지 테스트가 더 필요합니다
Michael Stum

2
GC는 0 세대가 "무언가 유휴 상태"가 아닌 특정 임계 값 (예 : 1MB)에 도달하면 메모리 할당시 트리거됩니다. 그렇지 않으면 단순히 객체를 할당하고 즉시 폐기하여 루프에서 OutOfMemoryException으로 끝날 수 있습니다.
liggett78

5
다른 프로세스가 필요하지 않으면 100 메가의 RAM이 낭비되지 않습니다. 그것은 당신에게 좋은 성능 향상을 제공합니다 :-P
Orion Edwards

9

최근에 수동 호출이 필요한 한 가지 경우 GC.Collect()는 작은 관리 C ++ 개체로 래핑 된 큰 C ++ 개체로 작업 할 때였으며, 이는 차례로 C #에서 액세스되었습니다.

사용 된 관리되는 메모리의 양이 무시할 수 있었기 때문에 가비지 수집기가 호출되지 않았지만 사용 된 관리되지 않는 메모리의 양이 엄청났습니다. Dispose()객체를 수동으로 호출 하려면 객체가 더 이상 필요하지 않은시기를 추적해야하는 반면, 호출 GC.Collect()하면 더 이상 참조되지 않는 객체가 정리됩니다 .....


6
이 문제를 해결하는 더 좋은 방법 GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)은 생성자와 나중에 종료 자 GC.RemoveMemoryPressure(addedSize)에서 호출하는 것 입니다 . 이렇게하면 수집 할 수있는 관리되지 않는 구조의 크기를 고려하여 가비지 수집기가 자동으로 실행됩니다. stackoverflow.com/questions/1149181/…
HugoRune

문제를 해결하는 더 좋은 방법은 실제로 Dispose ()를 호출하는 것입니다.
fabspro 2014 년

2
가장 좋은 방법은 Using 구조를 사용하는 것입니다. Try / Finally .Dispose는 번거 로움
TamusJRoyce 2015-06-05

7

나는 당신이 이미 모범 사례를 나열했다고 생각하며 정말로 필요하지 않는 한 그것을 사용하지 마십시오. 이러한 질문에 먼저 답해야 할 경우 잠재적으로 프로파일 링 도구를 사용하여 코드를 더 자세히 살펴볼 것을 강력히 권장합니다.

  1. 필요한 것보다 더 큰 범위에서 항목을 선언하는 코드가 있습니까?
  2. 메모리 사용량이 너무 많습니까?
  3. GC.Collect () 사용 전후의 성능을 비교하여 실제로 도움이되는지 확인하십시오.

5

프로그램에 메모리 누출이없고 객체가 축적되어 다음과 같은 이유로 Gen 0에서 GC-ed 될 수 없다고 가정합니다. 1) 오랫동안 참조되므로 Gen1 및 Gen2에 들어가십시오. 2) 큰 개체 (> 80K)이므로 LOH (Large Object Heap)로 이동합니다. 그리고 LOH는 Gen0, Gen1 및 Gen2 에서처럼 압축을 수행하지 않습니다.

".NET Memory"의 성능 카운터를 확인하면 1) 문제가 실제로 문제가 아님을 알 수 있습니다. 일반적으로 10 개의 Gen0 GC마다 1 개의 Gen1 GC가 트리거되고 10 개의 Gen1 GC마다 1 개의 Gen2 GC가 트리거됩니다. 이론적으로 GC0에 압력이 없으면 GC1 및 GC2는 GC-ed 될 수 없습니다 (프로그램 메모리 사용량이 실제로 연결되어있는 경우). 나에게는 결코 일어나지 않습니다.

문제 2)의 경우 ".NET Memory"성능 카운터를 확인하여 LOH가 비대 해지고 있는지 확인할 수 있습니다. 문제가 실제로 문제인 경우이 블로그에서 http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx를 제안하는 것처럼 대형 개체 풀을 만들 수 있습니다 .


4

큰 개체는 0 세대가 아닌 LOH (대형 개체 힙)에 할당됩니다. Gen 0으로 가비지 수집되지 않는다는 말이 맞습니다. 전체 GC주기 (0, 1, 2 세대)가 발생할 때만 수집됩니다.

즉, 다른 쪽에서는 GC가 큰 개체로 작업하고 메모리 압력이 높아질 때 더 적극적으로 메모리를 조정하고 수집 할 것이라고 믿습니다.

수집 여부와 상황을 말하기는 어렵습니다. 많은 컨트롤 등이있는 대화 창 / 양식을 폐기 한 후 GC.Collect ()를 수행했습니다. (양식과 해당 컨트롤이 비즈니스 개체의 많은 인스턴스를 생성하고 많은 데이터를로드하기 때문에 2 세대로 끝나기 때문입니다. 분명히 큰 물체), 그러나 실제로 그렇게함으로써 장기적으로 긍정적이거나 부정적인 영향을 느끼지 못했습니다.


4

추가하고 싶습니다 : GC.Collect () (+ WaitForPendingFinalizers ()) 호출은 이야기의 일부입니다. 다른 사람들이 올바르게 언급했듯이 GC.COllect ()는 비 결정적 컬렉션이며 GC 자체 (CLR)의 재량에 맡겨집니다. WaitForPendingFinalizers에 대한 호출을 추가하더라도 결정적이지 않을 수 있습니다. 이 msdn 링크 에서 코드를 가져 와서 개체 루프 반복을 1 또는 2로 사용하여 코드를 실행합니다. 비 결정적 의미를 찾을 수 있습니다 (개체의 소멸자에 중단 점 설정). 정확히, Wait .. ()에 의해 1 개 (또는 2) 개의 느린 객체가있을 때 소멸자는 호출되지 않습니다. [인용 요구]

코드가 관리되지 않는 리소스 (예 : 외부 파일 핸들)를 처리하는 경우 소멸자 (또는 종료 자)를 구현해야합니다.

다음은 흥미로운 예입니다.

참고 : MSDN에서 위의 예제를 이미 시도한 경우 다음 코드가 공기를 제거합니다.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

먼저 출력이 무엇인지 분석 한 다음 실행 한 다음 아래 이유를 읽으십시오.

{소멸자는 프로그램이 종료 된 후에 만 ​​암시 적으로 호출됩니다. } 객체를 결정적으로 정리하려면 IDisposable을 구현하고 Dispose ()를 명시 적으로 호출해야합니다. 그것이 본질입니다! :)


2

한 가지 더, GC Collect를 명시 적으로 트리거하면 프로그램 성능이 향상되지 않을 수 있습니다. 상황을 악화시킬 수 있습니다.

.NET GC는 적절하게 설계되고 조정되어 프로그램 메모리 사용량의 "습관"에 따라 GC0 / 1 / 2 임계 값을 조정할 수 있습니다. 따라서 일정 시간이 지나면 프로그램에 맞게 조정됩니다. GC.Collect를 명시 적으로 호출하면 임계 값이 재설정됩니다! 그리고 .NET은 프로그램의 "습관"에 다시 적응하는 데 시간을 투자해야합니다.

내 제안은 항상 .NET GC를 신뢰하는 것입니다. 모든 메모리 문제가 나타나면 ".NET Memory"성능 카운터를 확인하고 내 코드를 진단하십시오.


6
이 답변을 이전 답변과 병합하는 것이 더 좋습니다.
Salamander2007

1

이것이 모범 사례인지 확실하지 않습니다 ...

제안 : 확실하지 않은 경우이를 구현하지 마십시오. 사실이 알려진시기를 재평가 한 다음 성능 테스트 전 / 후를 수행하여 확인합니다.


0

그러나 Collect () 호출이 부정적인 영향을 미치지 않는지 확인하기 위해 코드를 안정적으로 테스트 할 수 있다면 계속 진행하십시오.

IMHO, 이것은 "당신의 프로그램이 미래에 어떤 버그도 가지지 않을 것이라는 것을 증명할 수 있다면, 계속하십시오 ..." 라고 말하는 것과 비슷합니다 .

진지하게 GC를 강제하는 것은 디버깅 / 테스트 목적에 유용합니다. 다른 시간에해야한다고 느끼면 착각했거나 프로그램이 잘못 빌드 된 것입니다. 어느 쪽이든 솔루션은 GC를 강요하지 않습니다.


"그러면 당신이 착각했거나 당신의 프로그램이 잘못 구축되었습니다. 어느 쪽이든 솔루션은 GC를 강요하지 않습니다 ..."절대는 거의 항상 사실이 아닙니다. 이치에 맞는 몇 가지 예외적 인 상황이 있습니다.
롤백
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.