가비지 수집기는 어떻게 스택 오버플로를 피합니까?


23

그래서 가비지 컬렉터가 어떻게 작동하는지 생각하고 흥미로운 문제를 생각했습니다. 가비지 수집기는 모든 구조를 동일한 방식으로 통과해야 할 것입니다. 그들은 연결된 목록이나 균형 잡힌 나무 등을 가로 지르는 날씨를 알 수 없습니다. 또한 검색시 너무 많은 메모리를 사용할 수 없습니다. 가능한 한 가지 방법과 모든 구조를 통과한다고 생각할 수있는 유일한 방법은 이진 트리와 같은 방식으로 모두 재귀 적으로 통과하는 것입니다. 그러나 이것은 연결된 목록 또는 심지어 균형이 잘 잡힌 이진 트리에서 스택 오버플로를 발생시킵니다. 그러나 내가 사용한 가비지 수집 언어는 모두 그러한 경우를 다루는 데 아무런 문제가없는 것 같습니다.

드래곤 북에서는 "스캔되지 않은"종류의 대기열을 사용합니다. 기본적으로 구조를 재귀 적으로 순회하지 않고 너무 대기열로 표시 해야하는 항목을 추가 한 다음 끝에 표시되지 않은 각 항목을 삭제합니다. 그러나이 대기열이 매우 커지지 않습니까?

가비지 수집기는 어떻게 임의의 구조를 통과합니까? 이 순회 기술은 어떻게 오버플로를 피합니까?


1
GC는 모든 구조를 거의 같은 방식으로 통과하지만 매우 추상적 인 의미로만 답합니다 (답변 참조). 그들이 구체적으로 물건을 추적하는 방법은 교과서에서 찾을 수있는 기본 프레젠테이션보다 훨씬 더 정교합니다. 그리고 그들은 재귀를 사용하지 않습니다. 또한 가상 메모리의 경우 잘못된 구현은 GC 속도 저하로 나타나고 메모리 오버플로로는 거의 발생하지 않습니다.
babou

추적에 필요한 공간이 걱정됩니다. 그러나 추적되어 사용중인 것으로 알려진 메모리와 잠재적으로 회수 가능한 메모리를 구별하는 데 필요한 공간이나 구조는 어떻습니까? 이는 상당한 메모리 비용이있을 수 있으며 힙 크기에 비례 할 수 있습니다.
babou

16 바이트 이상의 객체 크기에서 비트 벡터로 수행되므로 오버 헤드가 최소 1000 배 줄어 듭니다.
Jake

이를 수행하는 방법은 여러 가지가 있으며 (답변 참조) 추적에도 사용할 수 있습니다. 그러면 질문에 대답 할 수 있습니다 (추천하는 스택이나 대기열이 아닌 비트 벡터 또는 비트 맵을 추적에 사용할 수 있음). 작은 물체에 많은 공간이있을 수있는 공간을 낭비하지 않는 한 모든 물체가 크다고 가정 할 수는 없으며 공간에 대해 걱정하지 않아도됩니다. 가상 메모리에 있으면 공간이 훨씬 덜 문제가되고 문제는 매우 다릅니다.
babou

답변:


13

저는 가비지 수집 전문가가 아닙니다. 이 답변은 기술의 예만 제공합니다. 가비지 수집 기술에 대한 대표적인 개요라고 주장하지 않습니다.

스캔되지 않은 대기열이 일반적으로 선택됩니다. 대기열은 잠재적으로 가장 깊은 데이터 구조만큼 커질 수 있습니다. 큐는 일반적으로 가비지 수집 스레드의 스택이 아닌 명시 적으로 저장됩니다.

노드의 하위 중 하나를 제외한 모든 노드가 스캔되면 스캔되지 않은 큐에서 노드를 제거 할 수 있습니다. 이것은 기본적으로 테일 콜 최적화입니다. 가비지 콜렉터는 휴리스틱을 포함하여 노드의 가장 깊은 하위 항목을 마지막으로 스캔하려고 시도 할 수 있습니다. 예를 들어 Lisp의 GC carcons앞에있는 의 를 스캔해야 합니다 cdr.

스캔되지 않은 큐를 유지하지 않는 한 가지 방법은 포인터를 제자리에 수정하여 자식이 일시적으로 부모를 가리 키도록하는 것입니다. 이것은 가비지 수집기 이외의 컨텍스트에서 사용되는 상수 메모리 트리 탐색 기술입니다. 이 기술의 단점은 GC가 데이터 구조를 순회하는 동안 데이터 구조가 유효하지 않으므로 GC가 세계를 중지해야한다는 것입니다. 이것은 거래 차단기가 아닙니다. 많은 가비지 수집기에는 세계를 멈추는 단계와 결과적으로 가비지를 놓칠 수있는 단계가 포함됩니다.


2
마지막 단락에 설명 된 기술을 종종 " 포인터 반전 "이라고합니다.
방황 논리

@WanderingLogic 네, 포인터 반전 은 내 대답에서 그것을 호출 한 방법입니다. Deutsch, Schorr 및 Waite (1967) 때문입니다. 그러나 상수 메모리에서 작동한다고 말하는 것은 올바르지 않습니다. p 포인터를 사용하여 각 셀에 여분의 비트 가 필요 하지만 비트 스택을 사용하여 줄일 수는 있습니다. 동일한 이유로 귀하가 참조하는 허용 된 답변이 정확하지 않거나 완전하지 않습니다. 로그2
babou

나는 이러한 추가 비트를 필요없이 사용자 정의 GC에서 포인터 반전을 사용; 비결은 메모리에있는 객체의 메모리 내 특수 표현을 사용하는 것이 었습니다. 즉, "헤더"오브젝트는 중간에 있었고 헤더 앞에 포인터 필드가 있고 뒤에 포인터가 아닌 필드가있었습니다 . 또한 모든 포인터가 정렬되었으며 헤더에는 항상 최하위 비트가 설정된 필드가 포함되었습니다. 따라서 포인터 역전 역 추적 중에 다음 포인터에 도달하고 객체로 완료했음을 알면 추가 비트없이 명확하게 수행 할 수 있습니다. 이 레이아웃은 OOP 상속도 지원했습니다.
Thomas Pornin

@ThomasPornin 비트 정보가 어딘가에 있다고 생각합니다. 문제는 어디입니까? 우리는 이것을 채팅에서 논의 할 수 있습니까? 나는 지금 떠나야하지만, 이것의 바닥에 가고 싶습니다. 아니면 웹에서 볼 수있는 설명이 있습니까?
바부

1
@babou와 토마스 제발
질 'SO- 그만 악

11

간단히 말해서 : 가비지 콜렉터는 재귀를 사용하지 않습니다. 그들은 본질적으로 두 세트를 결합하여 추적을 제어합니다 (결합 할 수 있음). 추적 및 셀 처리 순서는 관련이 없으므로 세트를 표현할 수있는 상당한 구현 자유를 제공합니다. 따라서 실제로 메모리 사용량이 매우 저렴한 많은 솔루션이 있습니다. 힙에 메모리가 부족할 때 GC가 정확하게 호출되므로 이는 필수입니다. 새로운 페이지를 쉽게 할당 할 수 있고, 적은 공간이 아니라 데이터 위치가 부족하기 때문에 가상 메모리가 큰 경우 상황이 약간 다릅니다 .

귀하의 질문이 적용되지 않는 참조 계산이 아닌 가비지 수집기 추적 을 고려하고 있다고 가정합니다 .

문제는 아직 추적되지 않은 포인터를 포함하는 액세스 가능한 메모리 셀의 세트 (추적되지 않은) 세트 를 추적 하기 위해 추적하는 메모리 비용에 초점을 맞추고 있습니다.이것은 가비지 수집에 대한 메모리 문제의 절반에 불과합니다 . GC는 또한 프로세스의 끝에서 다른 모든 셀을 회수하기 위해 액세스 가능한 것으로 밝혀진 모든 셀 의 세트 (방문 용) 의 다른 세트를 추적해야합니다 . 서로를 논의하는 것은 비용이 비슷하고 비슷한 솔루션을 사용하며 심지어 결합 될 수 있기 때문에 제한이 있습니다.V

첫 번째로 주목해야 할 것은 모든 추적 GC가 프로그램에서 액세스 가능한 메모리의 셀 방향 그래프의 체계적 탐색을 기반으로 동일한 추상적 모델을 따르는 것입니다. 이를 위해 다음 세트를 사용합니다.

  • 뮤 테이터에 의해 이미 액세스 가능한 것으로 밝혀진 셀 의 세트 (방문) , 즉 GC가 수행되는 프로그램 또는 알고리즘. 세트 V 는 두 개의 분리 된 서브 세트로 분할된다 : V = U T ;VVV=

  • 아직 추적되지 않은 포인터를 가진 방문한 셀 의 세트 (추적되지 않음);

  • 모든 포인터가 추적 된 방문 셀의 세트 (추적)

  • 우리는 또한 H 참고H 힙에있는 모든 세포의 집합 여부를 사용한다.

U , 또는 UV , 필요성 작업에 알고리즘 어떻게 든 표현한다.

이 알고리즘은 런타임 시스템에 알려진 일부 루트 포인터 (일반적으로 스택 할당 메모리의 포인터)에서 시작하여 추적되지 않은 집합 에있는 모든 셀을 넣습니다 (따라서 도 있음)습니다.V

그런 다음 수집기는 셀을 하나씩 가져와 각 셀 c의 모든 포인터를 확인합니다. 각 포인터에 대해 뾰족한 셀이 V에 있으면 아무 것도 수행되지 않습니다. 그렇지 않으면 뾰족한 셀이 포인터를 아직 확인하지 않았으므로 U에 추가됩니다 . 모든 포인터가 처리되면 셀 c 는 추적되지 않은 집합 U 에서 추적 집합으로 전송됩니다.기음V기음 됩니다.

가 비어 있으면 추적이 종료됩니다 . 어떤 셀도 U를 두 번 이상 통과하지 않기 때문에 이것은 일어날 수밖에 없습니다 . 그 점에서 V = T , 그리고 모든 셀 V 따라서, 프로그램에 액세스 할 수 알고있는 교정되지 않습니다. 보체 H - VV=VHV 힙 셀이 테이터 프로그램에 의해 연결할 수있는 상기 테이터에 장래 할당을 위해 집에서 회수 될 수 있는지를 판정한다.V

couse 중에서 세부 사항은 세트가 구현되는 방식과 U 또는 U 및 여부에 따라 다릅니다.V되는 방식과 효과적으로 표현되는 T.

또한 셀이 무엇인지, 하나의 크기에 관계없이, 셀에 포인터를 찾는 방법, 압축 방법, 가비지 수집에 관한 서적 및 설문 조사에서 찾을 수있는 기타 기술적 인 문제에 대한 세부 정보는 생략합니다. .

이것이 매우 간단한 알고리즘임을 알 수 있습니다. 재귀는 없지만 세트 의 요소에는 루프 가 끝날 때까지 처리 될 때까지 커질 수 있는 루프 만 있습니다 .여분의 메모리에 대한 선험적 인 가정은 없습니다. 세트를 식별하고 필요한 작업을 저렴하게 수행 할 수있는 모든 것. 셀이 처리되는 순서는 관련이 없으며 (푸시 다운 스택이 필요 없음), 세트를 효율적으로 나타내는 수단을 선택할 수있는 많은 자유가 있습니다.

알려진 구현이 다른 경우 이러한 세트가 실제로 표현되는 방식이 다릅니다. 실제로 많은 기술이 사용되었습니다 :

  • 비트 맵 : 각 메모리 셀에 대해 하나의 비트가있는 맵에 대해 일부 메모리 공간이 유지되며 셀의 주소를 사용하여 찾을 수 있습니다. 해당 셀이 맵에 의해 정의 된 세트에있을 때 비트가 켜집니다. 비트 맵 만 사용하는 경우 셀당 2 비트 만 필요합니다.

  • 또는 각 셀에 특수 태그 비트 (또는 2)를 표시 할 공간이있을 수 있습니다.

  • 로그2 셀당 포인터의 개수를, 이는 상기 비트 스택에 의해 감소된다.

  • 셀의 내용 및 해당 포인터에 대한 술어를 테스트 할 수 있습니다.

  • 표시된 세트에 속하는 모든 셀을위한 메모리의 여유 부분에 셀을 재배치 할 수 있습니다.

  • V

  • 단일 세트에 대해서도 이러한 기술을 실제로 결합 할 수 있습니다.

말했듯이, 위의 모든 것은 일부 구현 된 가비지 수집기에 의해 사용되었습니다. 그것은 모두 구현의 다양한 제약에 달려 있습니다. 또한 메모리 사용량이 다소 저렴할 수 있으며, 최종 결과에 중요하지 않기 때문에 해당 목적에 따라 자유롭게 선택할 수있는 주문 정책처리 함으로써 도움이 될 수 있습니다.

새로운 영역에서 세포를 옮기는 가장 이상한 것처럼 보이는 것이 실제로는 매우 일반적입니다.이를 복제 수집이라고합니다. 가상 메모리와 함께 주로 사용됩니다.

분명히 재귀가 없으며 뮤 테이터 알고리즘 스택을 사용할 필요가 없습니다.

또 다른 중요한 점은 많은 현대 GC가 큰 가상 메모리를 위해 구현 된다는 입니다. 그런 다음 새로운 페이지를 쉽게 할당 할 수 있으므로 구현할 공간과 추가 목록 또는 스택을 얻는 것은 문제가되지 않습니다. 그러나 가상 메모리가 크면 공간이 부족하지 않고 지역성이 부족 합니다. 그런 다음 집합을 나타내는 구조와 그 용도 는 데이터 구조 및 GC 실행 의 로컬 성 을 유지하도록 설계되어야합니다 . 문제는 공간이 아니라 시간입니다. 부적절한 구현은 메모리 오버플로보다 수용 할 수없는 속도 저하를 보일 가능성이 높습니다.

나는 충분히 길어 보이기 때문에 이러한 기술의 다양한 조합으로 인해 많은 특정 알고리즘에 대한 언급을하지 않았다.


4

스택 오버플로를 피하는 표준 방법은 명시 적 스택 (힙에 데이터 구조로 저장)을 사용하는 것입니다. 그것은 이러한 목적으로도 작동합니다. 가비지 콜렉터는 종종 검사 / 탐색해야하는 항목의 작업 목록을 가지고 있으며이 역할을 수행합니다. 예를 들어, "스캔되지 않은"대기열은 정확히 이런 종류의 패턴의 예입니다. 대기열은 잠재적으로 커질 수 있지만 스택 세그먼트에 저장되어 있지 않기 때문에 스택 오버플로가 발생하지 않습니다. 어쨌든 힙에있는 라이브 오브젝트 수보다 크지 않습니다.


GC가 호출되면 힙이 가득 찼습니다. 또 다른 요점은 스택과 힙이 동일한 메모리 공간의 양쪽 끝에서 커진다는 것입니다 ..
babou

4

가비지 수집에 대한 "클래식"설명 (예 : Mark Wilson, " 단일 프로세서 가비지 수집 기술 ", 국제 메모리 관리 워크샵 , 1992, ( 대체 링크 ) 또는 Andrew Appel의 Modern Compiler Implementation에 대한 설명 (Cambridge University Press, 1998))에서 수집가는 "마크 및 스윕"또는 "복사"로 분류됩니다.

Mark and Sweep 수집기는 @Gilles의 답변에 설명 된대로 포인터 반전을 사용하여 추가 공간이 필요하지 않습니다. Appel은 Knuth가 포인터 반전 알고리즘을 Peter Deutsch, Herbert Schorr 및 WM Waite에 귀속한다고 말합니다.

가비지 컬렉터 복사는 종종 Cheyney의 알고리즘을 사용합니다. 하는 것을 사용하여 추가 공간이 없어도 큐 순회를 수행합니다. 이 알고리즘은 CJ Cheyney, "비 재귀 목록 압축 알고리즘", Comm. ACM , 13 (11) : 677-678, 1970.

복사 가비지 콜렉터에는 수집하려는 메모리 청크 ( from-space )와 사본에 사용중인 메모리 청크 ( to-space)가 있습니다. To-Space는 scan가장 오래된 복사 된 스캔되지 않은 레코드를 free가리키는 포인터 와 To -Space의 다음 자유 위치를 가리키는 포인터 와 함께 대기열로 구성됩니다 . Wilson의 논문에서이 그림은 다음과 같습니다.

Cheyney의 알고리즘 예

To-space에서 각 항목을 스캔 free할 때 하위 공간에서 하위 공간으로 포인터를 복사 한 다음 하위로 포인터를 하위 공간에서 하위 공간으로 복사합니다. 데이터 구조가 나무가 아닌 경우 (자녀가 둘 이상의 부모를 가질 수있는 경우) 사용해야 할 추가 트릭이 있습니다. 이 경우, 어린이를 공간에서 공간으로 복사 할 때 새 버전의 자식에 대한 전달 포인터 로 이전 버전의 자식을 덮어 써야합니다 . 그런 다음 이전 버전의 자식에 대한 다른 포인터를 스캔하면 이미 복사되었다는 것을 알고 다시 복사하지 않습니다.


실제로, 내 대답에서 설명했듯이 Mark + Sweep 및 Copy 모음은 모두 동일한 추상 그래프 알고리즘입니다. MS와 Copy 컬렉션은 추상 알고리즘에 사용되는 세트가 구현되는 방식에서만 차이가 있으며 두 제품군은 많은 변형과 함께 내 대답에 설명 된 세트 구현 기술의 일부 조합에 포함됩니다. 일부 GC 변형은 실제로 동일한 GC에서 MS와 Copy를 혼합합니다. 일부는 MS와 카피를 분리하는 것이 책을 편하게 구성 할 수있는 편리한 방법이라고 생각하지만, 그것은 임의적이며 구식 인 비전이라고 생각합니다.
babou

@babou : 방문한 모든 것이 복사되는 복사 알고리즘을 사용하는 경우 (느리지 만 작업 세트가 그다지 크지 않은 작은 플랫폼에서 유용 할 수 있음) 일부 알고리즘은 사용할 수 있기 때문에 다소 단순화 될 수 있습니다 이전에 재배치 된 객체가 사용했던 메모리는 스크래치 패드입니다. 각각의 읽기 전후에 객체의 유효성을 검사하고 객체가 이동 한 경우 전달 포인터를 따르는 경우 수집 중에 다른 스레드가 객체에 대한 읽기 전용 액세스를 수행하도록 제한된 기능을 얻을 수 있습니다.
supercat

@ supercat 나는 당신이 무엇을 말하려고하는지, 당신의 의도는 무엇인지 잘 모르겠습니다. 당신의 진술 중 일부는 정확 해 보입니다. 그러나 GC주기가 끝나기 전에 우주에서 어떻게 사용할 수 있는지 이해하지 못합니다 (포워딩 포인터 포함). 그리고 그것은 무엇을위한 스크래치 패드입니까? 알고리즘을 어떻게 단순화합니까? GC가 발생하는 동안 실행되는 다중 뮤 테이터 스레드와 관련하여 이는 심각한 직교 문제이지만 구현에 심각한 영향을 줄 수 있습니다. 나는 그 의견을 언급하려고 시도하지 않을 것입니다. 읽기 전용 액세스에서는 문제가 덜 발생하지만 악마는 세부 사항에 있습니다.
babou
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.