가비지 콜렉션을 강제 실행하는 것이 언제 좋은가?


135

그래서 거의 모든 단일 답변이 동일한 곳 에서 C # 가비지 수집기를 실행하는 것에 대한 질문 을 읽었 습니다 . 할 수는 있지만 매우 드문 경우를 제외하고는 안됩니다 . 슬프게도, 아무도 그러한 경우에 대해 자세히 설명하지 않습니다.

어떤 종류의 시나리오에서 실제로 가비지 수집을 강제하는 것이 좋거나 합리적인 아이디어인지 말해 줄 수 있습니까?

C # 전용 사례를 요구하지 않고 가비지 수집기가있는 모든 프로그래밍 언어를 요구합니다. Java와 같은 모든 언어에서 GC를 강요 할 수는 없지만 가능하다고 가정 해 봅시다.


17
"하지만 오히려 가비지 수집기가있는 모든 프로그래밍 언어" 다른 언어 (또는 더 적절하게는 다른 구현 )에서는 가비지 수집에 서로 다른 방법을 사용하므로 모든 크기의 규칙을 찾을 수 없을 것입니다.
Thirty Two 대령

4
@Doval 실시간 제약 조건 하에서 GC가 일치하는 보장을 제공하지 않으면, 당신은 바위와 힘든 장소 사이에 있습니다. 원하지 않는 일시 중지를 줄이거 나 아무것도하지 않는 것이 좋을지 모르지만 정상적인 작동 과정에서 할당을 피하는 것이 더 쉽다는 것을 들었습니다.

3
어려운 실시간 마감일을 기대하고 있다면 GC 언어를 사용하지 않을 것이라는 인상을 받았습니다.
GregRos

4
VM이 아닌 방식으로 어떻게 대답 할 수 있는지 알 수 없습니다. 32 비트 프로세스와 관련이 있으며 64 비트 프로세스와 관련이 없습니다. .NET JVM고급형
rwong

3
@DavidConrad 당신은 C #으로 강제 할 수 있습니다. 따라서 질문입니다.
Omega

답변:


127

모든 GC 구현 을 사용 하는 적절한 방법에 대해 포괄적 인 진술을 할 수는 없습니다 . 그들은 매우 다양합니다. 그래서 나는 당신이 처음에 언급 한 .NET과 이야기 할 것입니다.

논리 나 이유를 가지고이를 수행하려면 GC의 동작을 아주 잘 알고 있어야합니다.

내가 줄 수있는 유일한 조언은 : 절대로하지 마십시오.

GC의 복잡한 세부 사항을 진정으로 알고 있다면 내 조언이 필요하지 않으므로 중요하지 않습니다. 이미 100 % 확신을 모르는 경우 도움 및 온라인보고있다이 같은 답을 찾을 수 있습니다 : 당신은 GC.Collect를 호출해서는 안 , 또는 대안 : 당신은 어떻게 GC 작업의 세부 사항을 배우고 가야한다 안팎으로 만 답 을 알게 될 것 입니다.

GC를 사용하는 것이 합리적 인 안전한 장소가 하나 있습니다 .

GC.Collect는 사물 타이밍을 프로파일 링하는 데 사용할 수있는 API입니다. 두 번째 알고리즘이 결과를 왜곡하는 동안 첫 번째 알고리즘의 GC가 발생하지 않았다는 것을 알고 즉시 하나의 알고리즘을 프로파일 링하고 다른 알고리즘을 수집 및 프로파일 링 할 수 있습니다.

이런 종류의 프로파일 링은 수동으로 누군가에게 수집하는 것이 좋습니다.


어쨌든 고안된 예

하나의 가능한 사용 사례는 실제로 큰 물건을로드하는 경우 Gen 2로 곧바로 전달되는 Large Object Heap으로 끝나지만 Gen 2는 덜 자주 수집되기 때문에 수명이 긴 객체입니다. 어떤 이유로 든 수명이 짧은 객체를 Gen 2에로드하고 있다는 것을 알고 있다면 Gen 2를 더 작게 유지하고 컬렉션을 더 빨리 유지하기 위해 더 빨리 지울 수 있습니다.

이것은 내가 생각해 낼 수있는 가장 좋은 예이며 좋지 않습니다. 여기서 빌드하는 LOH 압력은 더 자주 수집하고 컬렉션은 너무 자주 발생합니다-LOH를 지우는 것처럼 임시 물체로 날려 버릴 때 나는 단순히 보다 훨씬 똑똑한 사람들에 의해 조정 된 GC 자체보다 더 나은 수집 빈도를 추정한다고 자신 을 믿지 않는다 .


.NET GC의 의미와 메커니즘에 대해 이야기 해 봅시다.

.NET GC에 대해 내가 생각하는 모든 것

여기에 오류가있는 사람은 나를 수정하십시오. 대부분의 GC는 흑 마법으로 잘 알려져 있으며, 불확실한 세부 정보를 남기려고했지만 여전히 문제가 있습니다.

아래에는 의도하지 않은 수많은 세부 정보와 내가 모르는 훨씬 더 많은 정보가 의도적으로 누락되어 있습니다. 이 정보는 귀하의 책임하에 사용하십시오.


GC 개념

.NET GC는 일관되지 않은 시간에 발생하기 때문에 "비 결정적"이라고 불리는 이유는 특정 시간에 발생하는 것에 의존 할 수 없다는 의미입니다. 또한 세대 가비지 수집기이므로 객체를 얼마나 많은 GC 패스로 통과했는지를 의미합니다.

Gen 0 힙의 오브젝트는 0 개의 콜렉션을 통해 살았으며, 새로 작성되어 최근 인스턴스화 이후 콜렉션이 발생하지 않았습니다. Gen 1 힙의 오브젝트는 하나의 콜렉션 패스를 통해 살았으며, 마찬가지로 Gen 2 힙의 오브젝트는 2 개의 콜렉션 패스를 통해 살았습니다.

이제 이러한 특정 세대와 파티션의 자격을 갖춘 이유를 주목할 가치가 있습니다. .NET GC는이 3 세대 만 인식하는데,이 3 가지 힙을 통과하는 콜렉션 패스는 모두 약간 다르기 때문입니다. 일부 개체는 수집 횟수가 수천 번 지속될 수 있습니다. GC는 이것들을 Gen 2 힙 파티션의 다른쪽에 남겨두고, 실제로 Gen 44이기 때문에 더 이상 어디에서나 파티션 할 필요는 없습니다. 그들에 대한 수집 패스는 Gen 2 힙의 모든 것과 동일합니다.

이러한 특정 세대에 대한 의미 론적 목적뿐만 아니라 이들을 존중하는 구현 된 메커니즘이 있습니다.


컬렉션에 포함 된 내용

GC 콜렉션 패스의 기본 개념은 힙 공간의 각 오브젝트를 검사하여 해당 오브젝트에 대한 라이브 참조 (GC 루트)가 있는지 확인하는 것입니다. 객체에 대한 GC 루트가 발견되면 현재 실행중인 코드가 여전히 해당 객체에 도달하여 사용할 수 있으므로 삭제할 수 없음을 의미합니다. 그러나 객체에 대한 GC 루트를 찾지 못하면 실행중인 프로세스에 더 이상 객체가 필요하지 않으므로 객체를 제거하여 새 객체에 대한 메모리를 확보 할 수 있습니다.

이제 많은 개체를 정리하고 일부를 남겨둔 후에는 불행한 부작용이 생길 수 있습니다. 죽은 개체가 제거 된 실제 개체 사이의 여유 공간 간격. 이 메모리 조각화 만 남겨두면 단순히 메모리를 낭비하게되므로 컬렉션은 일반적으로 "컴팩 션"이라는 작업을 수행하여 모든 라이브 객체를 남겨두고 힙에서 함께 압축하여 여유 메모리가 Gen의 힙 한쪽에 연속되도록합니다. 0.

이제 3 힙의 메모리에 대한 아이디어가 주어 졌는데, 모두 그들이 살아온 수집 패스 수로 분할되었으므로 이러한 파티션이 존재하는 이유에 대해 이야기합시다.


젠 0 컬렉션

당신이 안전하게 수집 할 수 있도록 - 세대 0은 절대 새로운 개체가되는 매우 작은 경향이 매우 자주 . 빈도는 힙이 작게 유지되도록하며 이러한 작은 힙을 통해 수집하기 때문에 수집 속도매우 빠릅니다 . 생성하는 대부분의 임시 개체는 매우 임시적이므로 임시 개체 는 더 이상 사용 직후 거의 사용되거나 참조되지 않으므로 수집 할 수 있습니다.


1 세대 컬렉션

이 빠지지 않았다 세대 1 인 객체 아주 아직도 - 생성 된 오브젝트의 광대 한 부분이 오랫동안 사용하지 않기 때문에 객체의 일시적 카테고리는 여전히 살아 오히려 짧은 될 수있다. 따라서 Gen 1은 다소 자주 수집하여 힙을 작게 유지하여 수집 속도가 빠릅니다. 그러나 가정은 적은 세대 0보다 그것의 객체의 임시 그래서 세대 0보다 자주 수집

나는 Gen 0의 수집 패스와 Gen 1의 수집 빈도 이외의 다른 기술 메커니즘이 있는지 솔직히 모른다고 말할 것입니다.


2 세대 컬렉션

2 세대는 이제 모든 힙의 어머니 여야합니까? 글쎄요, 맞습니다. 여기에는 모든 영구 객체가있는 곳이 Main()있습니다. 예를 들어 여러분이 살고 있는 객체 와 프로세스가 끝날 Main()때까지 Main()돌아올 때까지 참조되기 때문에 참조하는 모든 것입니다 .

Gen 2는 기본적으로 다른 세대가 수집 할 수 없었던 모든 것을위한 버킷이므로, 객체는 대체로 영구적이거나 오래 살았습니다. 따라서 Gen 2의 내용을 거의 인식하지 못하면 실제로 수집 할 수있는 것이기 때문에 자주 수집 할 필요가 없습니다. 이렇게하면 수집 빈도가 훨씬 낮아지기 때문에 수집 속도가 느려집니다. 따라서 기본적으로 이상한 시나리오에 대한 모든 추가 동작에 대해 다루었습니다. 실행 시간이 있기 때문입니다.


대형 객체 힙

2 세대의 추가 행동의 한 예는 점이다 또한 대형 개체 힙 컬렉션을 수행합니다. 지금까지 나는 Small Object Heap에 대해 전적으로 이야기했지만 .NET 런타임은 위의 압축으로 인해 특정 크기의 항목을 별도의 힙에 할당합니다. 압축하려면 작은 개체 힙에서 수집이 완료되면 개체를 움직여야합니다. Gen 1에 살아있는 10MB 객체가 있으면 수집 후 압축을 완료하는 데 훨씬 오래 걸리므로 Gen 1의 수집 속도가 느려집니다. 따라서 10mb 객체는 Large Object Heap에 할당되고 드물게 실행되는 Gen 2 중에 수집됩니다.


마무리

또 다른 예는 종료자가있는 객체입니다. .NETs GC (관리되지 않는 리소스) 범위를 벗어난 리소스를 참조하는 개체에 종료자를 배치합니다. 종료자는 GC가 관리되지 않는 리소스를 수집하도록 요구하는 유일한 방법입니다. 프로세스에서 누출되지 않도록 관리되지 않는 리소스의 수동 수집 / 제거 / 릴리스를 수행하도록 종료자를 구현하십시오. GC가 객체 파이널 라이저를 실행하면 구현시 관리되지 않는 리소스가 삭제되어 GC가 리소스 누수의 위험없이 객체를 제거 할 수 있습니다.

종료자가이를 수행하는 메커니즘은 종료 큐에서 직접 참조되는 것입니다. 런타임은 종료자를 사용하여 오브젝트를 할당 할 때 해당 오브젝트에 대한 포인터를 종료 큐에 추가하고 오브젝트를 제자리에 고정 (고정)하여 압축이 이동하지 않아 종료 큐 참조를 손상시킵니다. 수집 패스가 발생하면 결국 개체에 더 이상 GC 루트가없는 것으로 확인되지만 수집을 완료하기 전에 마무리를 실행해야합니다. 따라서 개체가 죽었을 때 컬렉션은 최종 큐에서 해당 참조를 이동하고 "FReachable"큐에 알려진 개체에 대한 참조를 배치합니다. 그런 다음 수집이 계속됩니다. 앞으로 또 다른 "비 결정적"시대에 Finalizer 스레드라고하는 별도의 스레드는 FReachable 대기열을 통과하여 참조 된 각 개체에 대해 finalizer를 실행합니다. 완료되면 FReachable 대기열이 비어 있으며 각 객체의 헤더에서 마무리 할 필요가 없다고 말하는 비트가 약간 뒤집 혔습니다 (이 비트는 수동으로 뒤집을 수도 있음)GC.SuppressFinalizeDispose()방법 에서 일반적입니다 ), 나는 또한 그것이 객체를 고정 해제 한 것으로 의심 하지만 그것에 대해 인용하지는 않습니다. 이 오브젝트가있는 힙에있는 다음 콜렉션은 결국이를 수집합니다. Gen 0 컬렉션은 마무리가 필요한 비트가 설정된 객체에주의를 기울이지 않아도 루트를 확인하지 않고도 자동으로 홍보합니다. 1 세대에서 파이널 라이즈가 필요한 루팅되지 않은 객체는 FReachable큐에 던져 질 것입니다 . 그러나 컬렉션은 아무 것도 수행하지 않으므로 2 세대에 존재합니다. 이러한 방식으로 파이널 라이저가 있고 그렇지 않은 모든 객체 GC.SuppressFinalize2 세대에 수집됩니다.


4
@FlorianMargaine 예 ... 모든 구현에서 "GC"에 대해 말하는 것은 실제로 의미가 없습니다 ..
Jimmy Hoffa

10
tl; dr : 대신 객체 풀을 사용하십시오.
Robert Harvey

5
tl; dr : 타이밍 / 프로파일 링에 유용 할 수 있습니다.
kutschkem

3
@ 기계 역학에 대한 위의 설명을 읽은 후에 (내가 이해하는 것처럼), 당신이 볼 때 어떤 이점이 있습니까? SOH (또는 LOH?)에서 많은 수의 물체를 청소합니까? 이 컬렉션에 대해 다른 스레드를 일시 중지 시켰습니까? 해당 컬렉션이 지운 것보다 Gen 2에 두 배나 많은 개체를 홍보 했습니까? 수집이 LOH에서 압축을 유발 했습니까 (켜져 있습니까?)? 서버 또는 데스크톱 모드의 GC 힙은 몇 개입니까? GC는 페켄의 빙산이며, 배신은 물 아래에 있습니다. 명확하게 조종하십시오. 나는 편안하게 모을만큼 똑똑하지 않다.
Jimmy Hoffa

4
@RobertHarvey 객체 풀도 은총 알이 아닙니다. 가비지 수집기의 생성 0은 이미 효과적으로 개체 풀입니다. 일반적으로 가장 작은 캐시 수준에 맞게 크기가 조정되므로 일반적으로 캐시에있는 메모리에 새 개체가 만들어집니다. 이제 객체 풀이 캐시에 대한 GC 보육원과 경쟁하고 있으며 GC 보육원과 풀의 합이 캐시보다 큰 경우 분명히 캐시 누락이 발생합니다. 그리고 병렬 처리를 사용하려면 동기화를 다시 구현하고 허위 공유에 대해 걱정해야합니다.
Doval

68

슬프게도, 아무도 그러한 경우에 대해 자세히 설명하지 않습니다.

몇 가지 예를 들어 보겠습니다. GC를 강요하는 것이 좋은 아이디어는 아니지만 드물게 가치가 있습니다. 이 답변은 .NET 및 GC 문헌에 대한 나의 경험에서 비롯된 것입니다. 다른 플랫폼 (적어도 중요한 GC가있는 플랫폼)으로 일반화해야합니다.

  • 다양한 종류의 벤치 마크 . 벤치 마크가 시작될 때 GC가 무작위로 트리거되지 않도록 알려진 관리되는 힙 상태를 원합니다. 벤치 마크를 반복 할 때 각 반복에서 동일한 수와 양의 GC 작업을 원합니다.
  • 자원의 갑작스런 릴리스. 예를 들어, 중요한 GUI 창을 닫거나 캐시를 새로 고치면 잠재적으로 오래된 큰 캐시 내용이 해제됩니다. GC는 참조를 null로 설정하기 때문에이를 감지 할 수 없습니다. 이것이 전체 객체 그래프를 분리한다는 사실은 쉽게 감지 할 수 없습니다.
  • 유출 된 관리되지 않는 리소스의 해제 . 물론 이런 일이 발생해서는 안되지만 타사 라이브러리가 COM 객체와 같은 물건을 유출하는 경우를 보았습니다. 개발자는 때때로 컬렉션을 유도해야했습니다.
  • 게임과 같은 대화 형 응용 프로그램 . 게임 중에는 프레임 당 매우 엄격한 시간 예산이 있습니다 (60Hz => 16ms 프레임 당). hickup을 피하려면 GC를 처리하는 전략이 필요합니다. 그러한 전략 중 하나는 G2 GC를 가능한 한 많이 지연시키고 로딩 화면 또는 컷 장면과 같은 적절한 시간에 강제로 실행하는 것입니다. GC는 그러한 최고의 순간이 언제인지 알 수 없습니다.
  • 대기 시간 제어 일반적이다. 일부 웹 응용 프로그램은 GC를 비활성화하고 주기적으로로드 밸런서 회전을 전환하는 동안 G2 수집을 실행합니다. 그렇게하면 G2 대기 시간이 사용자에게 절대로 노출되지 않습니다.

처리량이 목표 인 경우 GC가 더 적을수록 좋습니다. 이러한 경우 컬렉션을 강제 실행하면 긍정적 인 영향을 줄 수 없습니다 (실제 개체에 산재한 죽은 개체를 제거하여 CPU 캐시 사용률을 높이는 것과 같은 다소 고안된 문제는 제외). 일괄 수집은 내가 아는 모든 수집가에게 더 효율적입니다. 정상 상태 메모리 소비의 프로덕션 앱의 경우 GC를 유도해도 도움이되지 않습니다.

위에 주어진 예제는 메모리 사용의 일관성과 한계를 목표로합니다. 이러한 경우에 유도 된 GC는 의미가 있습니다.

GC가 실제로 최적 일 때마다 수집을 유도하는 신성 존재라는 널리 퍼져있는 아이디어가있는 것 같습니다. 내가 아는 GC는 정교하지 않으며 실제로 GC에 최적 인 것은 매우 어렵다. GC는 개발자보다 잘 알고 있습니다. 휴리스틱은 메모리 카운터 및 수집 속도 등과 같은 것들을 기반으로합니다. 휴리스틱은 일반적으로 좋지만 많은 양의 관리되는 메모리 해제와 같은 응용 프로그램 동작의 갑작스러운 변경 사항은 캡처하지 않습니다. 또한 관리되지 않는 리소스 및 대기 시간 요구 사항에 대해 눈을 멀게합니다.

GC 비용은 힙 크기 및 힙의 참조 수에 따라 다릅니다. 작은 힙에서는 비용이 매우 작을 수 있습니다. 1GB 힙 크기의 프로덕션 앱에서 .NET 4.5 1-2GB / sec의 G2 수집 속도를 보았습니다.


대기 시간 제어 사례의 경우 주기적으로 수행하는 대신 필요에 따라 (예 : 메모리 사용량이 특정 임계 값 이상으로 증가하는 경우) 수행 할 수 있습니다.
Paŭlo Ebermann

3
마지막 단락에서 +1 어떤 사람들은 컴파일러에 대해 같은 감정을 가지고 있으며 거의 ​​모든 것을 "조기 최적화"라고 부릅니다. 나는 보통 그들에게 비슷한 것을 말한다.
Honza Brabec

2
해당 단락에 대해서도 +1입니다. 사람들이 다른 사람이 작성한 컴퓨터 프로그램이 자신의 프로그램의 성능 특성을 반드시 자신보다 잘 이해해야한다고 생각하는 것은 충격적입니다.
Mehrdad

1
@HonzaBrabec 문제는 두 경우 모두 동일 합니다. GC 나 컴파일러보다 더 잘 알고 있다고 생각 되면 자신을 다치게하기 쉽습니다. 실제로 더 많은 것을 알고 있다면 조기에 그렇지 않다는 것을 알고있을 때에 만 최적화하는 것입니다.
svick

27

일반적으로 가비지 수집기는 "메모리 압력"에 도달하면 수집하며, 성능 문제가 발생하거나 프로그램 실행시 눈에 띄게 일시 중지 될 수 있으므로 다른 시간에 수집하지 않는 것이 좋습니다. 사실, 첫 번째 점은 두 번째 점에 의존합니다. 세대 별 가비지 수집기의 경우 최소한 가비지 대 좋은 개체의 비율이 높아질수록 프로그램을 일시 중지하는 데 소요되는 시간을 최소화하기 위해 더 효율적으로 실행 합니다. , 쓰레기를 가능한 한 많이 미루고 쌓아야합니다.

가비지 수집기를 수동으로 호출하는 적절한 시간은 1) 많은 가비지를 생성했을 가능성이 있고 2) 사용자가 약간의 시간이 걸리고 시스템이 응답하지 않는 것으로 예상되는 작업을 완료 한 시점입니다. 어쨌든. 고전적인 예는 큰 무언가 (문서, 모델, 새로운 레벨 등)를로드 할 때입니다


12

아무도 언급하지 않은 한 가지는 Windows GC가 놀랍도록 훌륭하지만 Xbox의 GC는 가비지입니다 (말장난) .

따라서 XBox에서 실행되도록 XNA 게임을 코딩 할 때 적절한 순간에 가비지 수집 시간을 지정하는 것이 절대적으로 중요합니다. 또한 XBox에서는 struct가비지 수집이 필요한 개체 수를 최소화하기 위해 평소보다 자주 사용하는 것이 일반적입니다.


4

가비지 콜렉션은 무엇보다도 메모리 관리 도구입니다. 따라서 메모리 가중이 발생하면 가비지 수집기가 수집됩니다.

최신 가비지 수집기는 매우 훌륭하고 나아 지므로 수동으로 수집하여 개선 할 수는 없습니다. 오늘 상황을 개선 할 수 있다고해도 선택한 가비지 수집기의 향후 개선으로 인해 최적화가 비효율적이거나 심지어 비생산적 일 수도 있습니다.

그러나 가비지 수집기는 일반적으로 메모리 이외의 리소스 사용을 최적화하지 않습니다. 가비지 수집 환경에서 가장 중요한 비 메모리 리소스에는 close메서드 나 이와 유사한 방법이 있지만 기존 API와의 호환성 등 어떤 이유로 든 그렇지 않은 경우가 있습니다.

이러한 경우 귀중한 비 메모리 리소스가 사용되고 있음을 알 때 가비지 수집을 수동으로 호출하는 것이 좋습니다.

RMI

이에 대한 한 가지 구체적인 예는 Java의 원격 메소드 호출입니다. RMI는 원격 프로 시저 호출 라이브러리입니다. 일반적으로 클라이언트가 다양한 개체를 사용할 수 있도록하는 서버가 있습니다. 서버가 클라이언트가 객체를 사용하지 않는 것을 알고 있으면 해당 객체는 가비지 수집 대상이됩니다.

그러나 서버가 이것을 아는 유일한 방법은 클라이언트가 클라이언트에게 알려주는 것이고 클라이언트는 서버가 클라이언트가 사용하는 모든 것을 가비지 수집 한 후에는 더 이상 객체가 필요하지 않다고 알려줍니다.

클라이언트에 사용 가능한 메모리가 많을 수 있으므로 가비지 수집이 매우 자주 실행되지 않을 수 있으므로 문제가 발생합니다. 한편, 서버에는 메모리에 사용되지 않은 많은 개체가있을 수 있으며 클라이언트가 사용하지 않는 것을 알지 못하므로 수집 할 수 없습니다.

RMI의 솔루션은 메모리가 많은 경우에도 클라이언트가 가비지 콜렉션을 주기적으로 실행하여 오브젝트가 서버에서 즉시 수집되도록하는 것입니다.


"이 경우 귀중한 비 메모리 리소스가 사용되고 있음을 알 때 가비지 수집을 수동으로 호출하는 것이 합리적 일 수 있습니다."비 메모리 리소스를 사용하는 경우 using블록을 사용하거나 다른 Close방법으로 메서드를 호출 해야합니다. 가능한 빨리 자원을 폐기하십시오. 메모리가 아닌 리소스를 정리하기 위해 GC에 의존하는 것은 신뢰할 수 없으며 모든 종류의 문제를 유발합니다 (특히 액세스를 위해 잠겨 야하는 파일은 한 번만 열 수 있음).
Jules

답변에 명시된 바와 같이 close방법을 사용할 수 있거나 (또는 ​​자원을 using블록 과 함께 사용할 수있는 경우) 올바른 방법입니다. 대답은 특히 이러한 메커니즘을 사용할 수없는 드문 경우를 다룹니다.
James_pic

내 개인적인 의견은 메모리가 아닌 리소스를 관리하지만 밀접한 방법을 제공 하지 않는 인터페이스는 사용 해서는 안되는 인터페이스라는 것입니다.이를 안정적으로 사용할 수있는 방법이 없기 때문입니다.
Jules

@Jules 동의하지만 때로는 피할 수 없습니다. 때때로 추상화가 누출되고 추상화가없는 것을 사용하는 것보다 누출이있는 추상화를 사용하는 것이 좋습니다. 때로는 유지할 수 없다는 것을 약속하는 레거시 코드로 작업해야 할 때가 있습니다. 예, 드물고 가능한 경우 피해야하며 가비지 수집을 강제하는 데 대한 모든 경고가있는 이유가 있지만 상황이 발생하고 OP는 이러한 상황이 어떻게 보이는지 묻습니다. .
James_pic

2

모범 사례는 대부분의 경우 가비지 콜렉션을 강제 실행하지 않는 것입니다. (내가 작업 한 모든 시스템은 가비지 수집을 강제로 수행했으며, 해결하면 가비지 수집을 강제 할 필요성을 제거하고 시스템 속도를 크게 향상시키는 문제를 강조했습니다.)

있습니다 몇 가지 경우는 경우 다음 가비지 컬렉터가하는 메모리 사용에 대한 자세한 내용을 알고. 다중 사용자 응용 프로그램이나 한 번에 하나 이상의 요청에 응답하는 서비스에서는 그렇지 않을 수 있습니다.

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

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

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

또 다른 경우는 일부 항목을 처리하기 위해 몇 분마다 깨우는 서비스이며 잠자는 동안 상태를 유지하지 않는 서비스입니다 . 그럼 그냥 자러 가기 전에 전체 수집을 강제 할 수 있습니다 보람.

컬렉션을 강제로 고려할 수있는 유일한 시간은 최근에 많은 객체가 생성되었고 현재 참조되는 객체가 거의 없다는 것을 알 때입니다.

나는 GC를 스스로 강요하지 않고이 유형의 것에 대한 힌트를 줄 수있을 때 가비지 수집 API를 사용하려고합니다.

" Rico Mariani의 성능 팁 " 도 참조하십시오.


2

gc ()를 직접 호출 할 수있는 몇 가지 경우가 있습니다.

  • [ 일부 사람들은 이것이 나에게 동의하는 오래된 세대 공간으로 물건을 승격시킬 수 있기 때문에 좋지 않다고 말합니다. 그러나 승격 될 수있는 객체가 항상 있다는 것은 항상 사실 이 아닙니다 . 이 gc()호출 후에도 이전 세대 공간으로 이동할 수있는 객체는 거의 없습니다. ] 큰 객체 모음을 만들고 많은 메모리를 사용하려고 할 때. 가능한 한 많은 공간을 준비하기 만하면됩니다. 이것은 상식입니다. gc()수동으로 호출 하면 메모리에로드하는 개체의 큰 모음 중 중복 참조 그래프 검사가 수행되지 않습니다. 즉, gc()메모리에 많은 양을로드하기 전에 실행 하면gc() 로딩 중 메모리 압력을 생성하기 시작할 때 적어도 한 번은로드 중에 발생합니다.
  • 컬렉션 로드를 완료하면더 많은 객체를 메모리에로드하지 않을 것입니다. 요컨대, 단계 작성에서 단계 사용으로 이동합니다. gc()구현에 따라 호출 하면 사용되는 메모리가 압축되어 캐시 위치가 크게 향상됩니다. 이로 인해 프로파일 링에서 얻을 수없는 성능이 크게 향상됩니다 .
  • 첫 번째와 비슷하지만 gc()메모리 관리 구현이 지원하는 경우 물리적 메모리의 연속성이 훨씬 향상됩니다. 이렇게하면 새로운 대규모 개체 컬렉션이 더욱 연속적이고 컴팩트 해져 성능이 향상됩니다.

1
누군가가 공감의 이유를 지적 할 수 있습니까? 나는 스스로 대답을 판단하기에 충분하지 않습니다 (처음에는 그것이 나에게 의미가 있습니다).
Omega

1
세 번째 요점에 대한 공감대가 있다고 생각합니다. "이것은 단지 상식입니다"라고 말할 가능성도 있습니다.
immibis

2
큰 개체 컬렉션을 만들 때 GC는 컬렉션이 필요한지 알기에 충분히 똑똑해야합니다. 메모리를 압축해야 할 때와 동일합니다. 관련 객체의 메모리 위치를 최적화하기 위해 GC에 의존하는 것은 현실적으로 불가능합니다. 다른 솔루션 (구조, 안전하지 않은 ...)을 찾을 수 있다고 생각합니다. (나는 downvoter가 아닙니다).
기 illa

3
괜찮은 시간에 대한 첫 번째 아이디어는 내 의견으로는 나쁜 조언입니다. 최근에 수집품이있을 가능성이 높으므로 다시 수집하려는 시도 는 단순히 객체를 이후 세대로 임의로 승격 시키려고하므로 거의 항상 나쁩니다. 후기 세대에는 컬렉션을 시작하는 데 시간이 오래 걸리고 힙 크기를 늘리면 "가능한 한 많은 공간을 비우기"때문에 더 문제가됩니다. 또한로드로 메모리 압력을 증가시키려는 경우 어쨌든 컬렉션을 유도하기 시작할 가능성이 높습니다. Gen1 / 2가 증가했기 때문에 실행 속도가 느려질 것입니다.
Jimmy Hoffa

2
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.행 확률에 많은 톤의 객체를 할당하면 이미 압축되어 있습니다. 어떤 것이라도 가비지 콜렉션은 약간 뒤섞 일 수 있습니다. 어느 쪽이든, 밀도가 높고 메모리에서 임의로 점프하지 않는 데이터 구조를 사용하면 더 큰 영향을 미칩니다. 노드 당 순진한 하나의 요소 당 링크 된 목록을 사용하는 경우 수동 GC 속임수가 그 정도를 차지하지 않습니다.
Doval

2

실제 예 :

거의 변경되지 않았으며 매우 빠르게 액세스해야하는 매우 큰 데이터 세트를 사용하는 웹 응용 프로그램이있었습니다 (AJAX를 통한 키 입력 당 빠른 응답).

여기서해야 할 분명한 일은 관련 그래프를 메모리에로드하고 데이터베이스가 아닌 여기에서 액세스하여 DB가 변경 될 때 그래프를 업데이트하는 것입니다.

그러나 매우 크면 순진한로드로 인해 향후 증가하는 데이터로 인해 최소 6GB의 메모리를 차지했을 것입니다. (내 2GB 시스템이 6GB 이상을 처리하려고한다는 것이 확실 해지면 정확히 작동하지 않는다는 것을 알기 위해 필요한 모든 측정 결과를 얻었습니다.)

운 좋게도,이 데이터 세트에는 서로 같은 많은 수의 아이스 불변의 객체가있었습니다. 일단 특정 배치가 다른 배치와 동일하다는 사실을 알아 낸 후에는 하나의 참조를 다른 참조와 별칭을 지정하여 많은 데이터를 수집 할 수 있으므로 모든 것을 반으로 줄였습니다.

모든 것이 좋고 좋았지 만, 여전히이 상태에 도달하기 위해 약 0.5 분의 공간에 6GB 이상의 객체가 쏟아져 나옵니다. GC는 그 자체로 남겨 두지 않았다. 응용 프로그램의 일반적인 패턴에 비해 활동이 급증했습니다 (초당 할당 해제에서 훨씬 덜 무거움).

따라서이 GC.Collect()빌드 프로세스 중에 주기적으로 호출 하면 모든 것이 원활하게 작동했습니다. 물론 GC.Collect()응용 프로그램이 실행되는 나머지 시간을 수동으로 호출하지 않았습니다 .

이 실제 사례는 언제 사용해야하는지에 대한 지침의 좋은 예입니다 GC.Collect().

  1. 수집에 사용할 수있는 많은 개체가 비교적 드물게 사용되는 경우와 함께 사용합니다 (메가 바이트의 가치가 있었으며,이 그래프 작성은 응용 프로그램 수명 동안 주당 약 1 분 동안 매우 드문 경우였습니다.
  2. 성능 손실이 상대적으로 허용 가능한 경우 수행하십시오. 이것은 응용 프로그램 시작시에만 발생했습니다. (이 규칙의 또 다른 좋은 예는 게임 중 레벨 또는 플레이어가 약간의 일시 정지로 인해 기분이 상하지 않는 게임의 다른 지점 사이입니다).
  3. 실제로 개선이 있는지 확인하기위한 프로필입니다. (매우 쉬움; "작동"은 거의 항상 "작동하지 않습니다"를 능가합니다).

GC.Collect()포인트 1과 2가 적용되어 포인트 3이 상황을 악화 시키거나 적어도 개선하지 못했거나 개선이 거의 없거나 전혀 없었기 때문에 전화 할 가치 가있는 경우가 있다고 생각했던 대부분의 시간 접근 방식이 응용 프로그램의 수명 동안 더 나아질 가능성이 높기 때문에 전화를 걸지 말아야합니다.


0

쓰레기 처리에 다소 정통한 사용법이 있습니다.

C # 세계에서는 불행하게도 IDisposable-disposing으로 알려진 추악하고, 거칠고, 우아하지 않고, 오류가 발생하기 쉬운 관용구를 사용하여 객체 처리를 구현하는이 오도 된 관행이 있습니다 . MSDN 은 길이를 설명 하고 많은 사람들이 그것을 맹세하고 종교적으로 따르며 시간에 따라 정확히 어떻게 수행 해야하는지에 대해 토론 합니다.

(여기서 내가 못생긴 것을 부르는 것은 객체 처리 패턴 자체 가 아니며 , 못생긴 것을 부르는 것은 특정 IDisposable.Dispose( bool disposing )관용구입니다.)

당신의 객체의 소멸자는 항상 자원을 정리하기 위해 가비지 컬렉터에 의해 호출 될 것이라는 점을 보장하는 가정으로 불가능하기 때문에 사람들이 내 자원 정리를 수행 할 수 있도록이 관용구는, 발명되었다 IDisposable.Dispose(), 그리고 경우에 그들은 잊고, 또한 그것에서 한 번 더 시도해 소멸자 안에서. 만약을 대비해서

그러나 IDisposable.Dispose()관리 대상 객체와 관리되지 않는 객체를 정리해야 할 수도 있지만 IDisposable.Dispose()소멸자 내에서 호출 된 경우 관리 대상 객체를 정리할 수 없습니다 .이 시점에서 가비지 수집기에서 이미 처리했기 때문에 관리 대상 객체 는 관리되는 개체와 관리되지 않는 개체를 모두 정리해야하는지 또는 관리되지 않는 개체 만 정리해야하는지 알기 Dispose()위해 bool disposing플래그를 허용 하는 별도의 방법 이 필요 합니다.

실례 합니다만, 이것은 제정신이 아닙니다.

나는 아인슈타인의 공리로 간다. 그것은 가능한 한 단순하지만 단순하지 않아야한다고 말한다. 분명히, 우리는 자원 정리를 생략 할 수 없으므로 가능한 가장 간단한 해결책은 최소한 그 것을 포함해야합니다. 다음으로 가장 간단한 해결책은 대체물로 소멸자에 의존하여 물건을 복잡하게하지 않고 모든 것을 폐기 해야하는 정확한 시간에 항상 모든 것을 처분하는 것입니다.

엄밀히 말하면, 프로그래머가 호출을 잊는 실수를하지 않도록 보장하는 것은 불가능 IDisposable.Dispose()하지만, 우리가 할 수있는 일은 소멸자를 사용하여이 실수를 잡는 것입니다. 실제로 매우 간단 disposed합니다. 처분 가능한 객체 의 플래그가 결코로 설정되지 않은 것을 발견하면 소멸자가 수행해야 할 모든 로그 항목을 생성하는 것 true입니다. 따라서 소멸자를 사용하는 것은 폐기 전략의 핵심 부분이 아니라 품질 보증 메커니즘입니다. 그리고 이것은 디버그 모드 전용 테스트이므로 전체 소멸자를 #if DEBUG블록 안에 배치 할 수 있으므로 프로덕션 환경에서 파괴 페널티가 발생하지 않습니다. 합니다 ( IDisposable.Dispose( bool disposing )관용구는 규정GC.SuppressFinalize() 마무리의 오버 헤드를 줄이기 위해 정확하게 호출해야하지만 내 메커니즘을 사용하면 프로덕션 환경에서 해당 오버 헤드를 완전히 피할 수 있습니다.)

그것은 영구 하드 오류 대 소프트 오류 인수입니다. IDisposable.Dispose( bool disposing )관용구는 소프트 오류 접근 방식이며 Dispose()가능한 경우 시스템이 실패하지 않고 프로그래머가 호출하는 것을 잊어 버리는 시도를 나타냅니다 . 하드 오류 접근 방식은 프로그래머가 항상 Dispose()호출 해야한다고 말합니다 . 대부분의 경우 하드 오류 방식에 의해 일반적으로 규정 된 페널티는 어설 션 실패이지만,이 경우에는 예외를 만들고 오류 로그 항목의 간단한 발행에 대한 페널티를 줄입니다.

따라서이 메커니즘이 작동하려면 DEBUG 버전의 응용 프로그램이 종료하기 전에 전체 쓰레기 처리를 수행하여 모든 소멸자가 호출 IDisposable되도록하고 처리 하지 않은 객체를 잡아야합니다 .


Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()실제로 C #이 가능하다고 생각하지는 않지만 그렇지 않습니다. 자원을 노출시키지 마십시오. 대신, 당신이 그것으로 할 모든 것들 (기본적으로 모나드)을 설명하기위한 DSL을 제공하고, 자원을 획득하고, 일을하고, 해제하고, 결과를 반환하는 함수를 제공하십시오. 요령은 타입 시스템을 사용하여 누군가가 리소스에 대한 참조를 밀수하면 다른 run 함수 호출에서 사용할 수 없도록 보장하는 것입니다.
Doval

2
Dispose(bool disposing)정의되지 않은 문제 IDisposable는 개체가 필드로 가지고 있거나 관리해야하는 관리되는 개체와 관리되지 않는 개체를 정리하는 데 사용되어 잘못된 문제를 해결한다는 것입니다. 다른 일회용 객체로 관리되는 개체에 관리되지 않는 개체는 모두에 대해 다음 걱정하는 Dispose()방법이 그 중 하나 finaliser이없는 (폐기하거나 단지 관리가 객체 (같은 청소 필요한 경우 않는 finaliser이)입니다 중 전혀), 그리고 필요성이 bool disposing사라짐
Jon Hanna

종료가 실제로 작동하는 방식으로 인해 -1 나쁜 조언입니다. 나는 완전히 온 당신의 점에 동의 dispose(disposing)관용구 인 terribad하지만, 그들은 단지 (자원을 관리해야 할 때 사람들이 너무 자주 그 기술과 파이널 라이저를 사용하기 때문에 나는 그런 말을 DbConnection예를 들어 객체가되어 관리 가 pinvoked 아니에요 또는 COM이 정렬 화,), 그리고 당신은 SHOULD 관리되지 않거나, 암호를 입력하거나, COM을 마샬링하거나, 안전하지 않은 코드로 완성기를 구현하십시오 . 위의 답변에서 비싸지 않은 파이널 라이저가 얼마나 많은지, 클래스에 관리되지 않는 리소스 가 없으면 사용 하지 마십시오 .
Jimmy Hoffa

2
나는 많은 사람들이 dispose(dispoing)관용구 에서 핵심적인 중요한 것으로 생각하는 무언가를 버리기 때문에 거의 +1을주고 싶지만 진실은 사람들이 GC 물건을 무서워하기 때문에 너무 널리 퍼져 있습니다. 그 ( dispose단지 심지어 그것을 조사없이 처방 약을 복용 장점을 GC와는 제로가 있어야합니다). 그것을 조사해 주셔서 감사합니다. 그러나 가장 큰 전체를 놓쳤습니다. (파이널 라이저가 필요 이상으로 자주 울리는 것을 권장합니다)
Jimmy Hoffa

1
@JimmyHoffa 입력 해 주셔서 감사합니다. 필자는 종결자가 일반적으로 관리되지 않는 리소스를 배포하는 데만 사용해야한다는 데 동의 하지만 DEBUG 빌드에서는이 규칙을 적용 할 수 없으며 DEBUG 빌드에서는 버그를 잡기 위해 종료자를 자유롭게 사용할 수 있다는 데 동의하지 않습니까? 그것이 내가 여기서 제안하는 전부이므로, 왜 당신이 그것에 문제를 제기하는지 알 수 없습니다. 자바의 세계에서이 접근법에 대한 자세한 설명은 programmers.stackexchange.com/questions/288715/… 를 참조하십시오 .
마이크 나 키스

0

어떤 종류의 시나리오에서 실제로 가비지 수집을 강제하는 것이 좋거나 합리적인 아이디어인지 말해 줄 수 있습니까? C # 전용 사례를 요구하지 않고 가비지 수집기가있는 모든 프로그래밍 언어를 요구합니다. Java와 같은 모든 언어에서 GC를 강요 할 수는 없지만 가능하다고 가정 해 봅시다.

이론적으로 말하고 수집주기 동안 속도를 늦추는 일부 GC 구현과 같은 문제를 무시하는 것만으로 가비지 수집을 강요하는 가장 큰 시나리오는 포인터 충돌을 해결하기 위해 논리적 누수가 바람직합니다. 예상치 못한 시간에 인명이나 이런 종류의 비용이들 수 있습니다.

Flash 게임과 같은 GC 언어를 사용하여 작성된 더 순조로운 인디 게임을 보면 미친 것처럼 누출되지만 충돌하지는 않습니다. 게임 코드베이스의 일부가 null에 대한 참조를 설정하거나 목록에서 제거하는 것을 잊어 버렸기 때문에 20 분의 메모리가 게임을 시작하는 데 10 분이 걸릴 수 있지만 프레임 속도가 저하 될 수 있지만 게임은 여전히 ​​작동합니다. shoddy C 또는 C ++ 코딩을 사용하여 작성된 유사한 게임은 동일한 유형의 리소스 관리 실수로 매달려 포인터에 액세스 한 결과 충돌이 발생할 수 있지만 그렇게 많이 유출되지는 않습니다.

게임의 경우 충돌을 신속하게 감지하고 수정할 수 있다는 점에서 충돌이 바람직 할 수 있지만 미션 크리티컬 프로그램의 경우 완전히 예상치 못한 시간에 충돌하면 누군가를 죽일 수 있습니다. 따라서 충돌이 발생하지 않거나 다른 형식이 보안 인 시나리오가 가장 중요하다고 생각되는 경우는 논리적 누출이 비교적 사소한 것입니다.

GC를 강제하는 것이 좋지 않다고 생각하는 주요 시나리오는 논리적 누출이 실제로 충돌보다 바람직하지 않은 상황에 대한 것입니다. 예를 들어 게임의 경우 충돌로 인해 누군가를 죽일 필요는 없으며 내부 테스트 중에 쉽게 잡히고 고칠 수 있지만 제품이 배송 된 후에도 논리적 누출이 눈에 띄지 않으면 몇 분 안에 게임을 재생할 수 없습니다. . 일부 도메인에서는 테스트에서 발생하는 쉽게 재현 가능한 충돌이 누수보다 빠르면 즉시 눈에 띄지 않습니다.

팀에서 GC를 강제 실행하는 것이 바람직한 위치를 생각할 수있는 또 다른 경우는 명령 줄에서 실행 된 작업 중 하나를 수행 한 다음 종료하는 매우 짧은 프로그램입니다. 이 경우 프로그램 수명이 너무 짧아 모든 종류의 논리적 누수가 중요하지 않습니다. 큰 리소스의 경우에도 논리적 누출은 일반적으로 소프트웨어를 실행 한 후 몇 시간 또는 몇 분만에 문제가되므로 3 초 동안 만 실행되도록되어있는 소프트웨어는 논리적 누출에 문제가 없을 가능성이 높으며 많은 일이 발생할 수 있습니다 팀이 방금 GC를 사용한 경우 그러한 단기 프로그램을 작성하는 것이 더 간단합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.