모든 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.SuppressFinalize
Dispose()
방법 에서 일반적입니다 ), 나는 또한 그것이 객체를 고정 해제 한 것으로 의심 하지만 그것에 대해 인용하지는 않습니다. 이 오브젝트가있는 힙에있는 다음 콜렉션은 결국이를 수집합니다. Gen 0 컬렉션은 마무리가 필요한 비트가 설정된 객체에주의를 기울이지 않아도 루트를 확인하지 않고도 자동으로 홍보합니다. 1 세대에서 파이널 라이즈가 필요한 루팅되지 않은 객체는 FReachable
큐에 던져 질 것입니다 . 그러나 컬렉션은 아무 것도 수행하지 않으므로 2 세대에 존재합니다. 이러한 방식으로 파이널 라이저가 있고 그렇지 않은 모든 객체 GC.SuppressFinalize
2 세대에 수집됩니다.