Java 가비지 콜렉션은 순환 참조와 어떻게 작동합니까?


161

내 이해에서 Java의 가비지 수집은 해당 객체를 가리키는 다른 것이 없으면 일부 객체를 정리합니다.

내 질문은, 우리가 이와 같은 것을 가지고 있다면 어떻게 될까요?

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code

a, bc쓰레기 수집해야하지만, 그들은 다른 모든 객체에 의해 참조되고있다.

Java 가비지 콜렉션은이를 어떻게 처리합니까? (또는 단순히 메모리 소모입니까?)


1
stackoverflow.com/questions/407855/… , 특히 @gnud의 두 번째 답변을 참조하십시오 .
세스

답변:


161

Java의 GC는 가비지 콜렉션 루트에서 시작하는 체인을 통해 도달 할 수없는 경우 "가비지"로 간주되므로 이러한 오브젝트가 수집됩니다. 주기를 형성하기 위해 객체가 서로를 가리킬 수도 있지만 루트에서 잘라 내면 여전히 쓰레기입니다.

부록 A : Java 플랫폼 성능의 가비지 콜렉션에 대한 진실 : 전략 및 전술 에서 도달 할 수없는 오브젝트에 대한 섹션을 참조 하십시오.


14
당신은 그것에 대한 참조가 있습니까? 테스트하기가 어렵습니다.
tangens

5
참조를 추가했습니다. 객체의 finalize () 메서드를 재정 의하여 객체가 수집되는 시점을 확인할 수도 있습니다 (finalize ()를 사용하는 것이 권장하는 유일한 방법 임).
Bill the Lizard

1
마지막 주석을 명확히하기 위해 객체의 고유 ID를 인쇄하는 finalize 메소드에 디버그 인쇄 명령문을 넣으십시오. 서로를 참조하는 모든 개체가 수집되는 것을 볼 수 있습니다.
Bill the Lizard

4
"... 인식 할만큼 똑똑하다 ..."는 혼란스럽게 들린다. GC는 사이클을 인식 할 필요가 없습니다. 사이클에 도달 할 수 없기 때문에 쓰레기입니다.
Alexander Malakhov

86
@tangens "그것에 대한 참조가 있습니까?" 가비지 수집에 대한 토론에서. 베스트. 펀 이제까지.
Michał Kosmulski

139

예 Java 가비지 콜렉터가 순환 참조를 처리합니다!

How?

가비지 수집 루트 (GC 루트)라는 특수 객체가 있습니다. 이것들은 항상 접근 할 수 있으며 자체 루트에있는 모든 객체입니다.

간단한 Java 애플리케이션에는 다음과 같은 GC 루트가 있습니다.

  1. 주요 방법의 지역 변수
  2. 주요 실
  3. 메인 클래스의 정적 변수

여기에 이미지 설명을 입력하십시오

더 이상 사용하지 않는 오브젝트를 판별하기 위해 JVM은 mark-and-sweep 알고리즘 이라고하는 것을 간헐적으로 실행 합니다 . 다음과 같이 작동합니다

  1. 이 알고리즘은 GC 루트부터 시작하여 모든 객체 참조를 통과하고 발견 된 모든 객체를 살아있는 것으로 표시합니다.
  2. 표시된 오브젝트가 차지하지 않는 모든 힙 메모리가 재 확보됩니다. 단순히 사용되지 않는 것으로 표시되어 있으며 기본적으로 사용되지 않는 객체가 없습니다.

따라서 GC 루트에서 객체에 도달 할 수없는 경우 (자체 참조 또는 순환 참조 인 경우에도) 가비지 수집 대상이됩니다.

물론 프로그래머가 객체의 역 참조를 잊어 버린 경우 메모리 누수가 발생할 수 있습니다.

여기에 이미지 설명을 입력하십시오

출처 : 자바 메모리 관리


3
완벽한 설명! 감사! :)
Jovan Perovic

그 책을 연결해 주셔서 감사합니다. 이것과 다른 Java 개발 주제에 대한 훌륭한 정보로 가득합니다!
Droj

14
마지막 그림에는 도달 할 수없는 개체가 있지만 도달 가능한 개체 섹션이 있습니다.
La VloZ Merrill

13

가비지 수집기는 CPU 레지스터, 스택 및 전역 변수와 같이 항상 "연결 가능한"것으로 간주되는 일부 "루트"위치에서 시작합니다. 해당 영역에서 포인터를 찾고 그들이 가리키는 모든 것을 재귀 적으로 찾아서 작동합니다. 모든 것이 발견되면 다른 모든 것은 쓰레기입니다.

물론 속도를 위해 대부분 몇 가지 변형이 있습니다. 예를 들어, 대부분의 최신 가비지 수집기는 "세대 적"이므로 개체를 세대로 나눕니다. 개체가 오래되면 가비지 수집기는 해당 개체가 여전히 유효한지 여부를 파악하려고 시도하는 시간이 점점 길어집니다. -오래 살았다면 더 오래 살 가능성이 매우 높다고 가정하기 시작합니다.

그럼에도 불구하고, 기본 아이디어는 동일하게 유지됩니다. 그것은 당연한 것으로 생각되는 일부 루트 세트에서 시작하여 여전히 사용할 수있는 모든 포인터를 쫓는 것을 기반으로합니다.

흥미로운 점 : 사람들은 종종 가비지 수집기의이 부분과 원격 프로 시저 호출과 같은 객체의 마샬링 코드 간의 유사성에 놀랄 수 있습니다. 각각의 경우, 루트 객체 세트에서 시작하여 포인터를 쫓아 참조하는 다른 모든 객체를 찾습니다.


설명하는 것은 추적 수집기입니다. 다른 종류의 수집가가 있습니다. 이 토론에 특히 관심을 참조 카운팅 수집이다 않는 사이클에 문제가 경향이있다.
Jörg W Mittag

@ Jörg W Mittag : 확실히 사실입니다.하지만 참조 카운팅을 사용하는 (합리적으로 현재의) JVM을 알지 못하므로 원래 질문과는 크게 다른 것 같지는 않습니다.
Jerry Coffin

@ Jörg W Mittag : 최소한 기본적으로 Jikes RVM은 현재 지역 기반 추적 수집기 인 Immix 수집기를 사용한다고 생각합니다 (참조 계산도 사용함). 그 참조 계산을 참조하는지 또는 추적하지 않고 참조 계산을 사용하는 다른 수집기를 참조하는지 확실하지 않습니다 (Immix가 "리사이클 러"라고 부르는 것을 들어 본 적이 없기 때문에 후자를 추측합니다).
Jerry Coffin

나는 약간 혼란스러워했다 : Recycler는 Jalapeno에서 구현되었으며, 내가 생각했던 알고리즘은 Jikes에서 구현 된 Ulterior Reference Counting 입니다. 물론 Jikes가 이것을 사용한다고 말하지만 Jikes와 특히 MMtk는 동일한 JVM 내에서 다른 가비지 수집기를 신속하게 개발하고 테스트하도록 특별히 설계 되었기 때문에 가비지 수집기는 매우 쓸모가 없다고 말합니다.
Jörg W Mittag

2
Ulterior Reference Counting은 2003 년에 Immix를 디자인 한 사람들이 2003 년에 디자인 한 것이므로 후자가 전자를 대체 한 것으로 생각됩니다. URC는 다른 전략과 결합 될 수 있도록 특별히 설계되었으며, 실제로 URC 논문은 URC가 추적 및 참조 카운팅의 장점을 결합한 수집기로 향하는 디딤돌이라고 명시 적으로 언급합니다. 나는 Immix가 그 수집기라고 생각합니다. 여하튼, 리사이클이 인 순수한 불구하고 검출 할 수있는 기준 계수 수집하고 수집 사이클 : WWW.Research.IBM.Com/people/d/dfb/recycler.html
르그 W MITTAG

13

당신이 올바른지. 설명하는 특정 형태의 가비지 콜렉션을 " 참조 횟수 "라고합니다. 가장 간단한 경우에 개념적으로 작동하는 방법 (적어도, 대부분의 최신 참조 횟수 구현은 실제로는 다르게 다르게 구현 됨)은 다음과 같습니다.

  • 객체에 대한 참조가 추가 될 때마다 (예 : 변수 또는 필드에 할당되거나, 메소드에 전달되는 등) 참조 횟수가 1 씩 증가합니다.
  • 객체에 대한 참조가 제거 될 때마다 (메소드가 반환되고 변수가 범위를 벗어남, 필드가 다른 객체에 다시 할당되거나 필드를 포함하는 객체가 가비지 수집 됨) 참조 횟수가 1 씩 감소합니다.
  • 참조 카운트가 0에 도달하면 더 이상 객체에 대한 참조가 없으므로 아무도 더 이상 사용할 수 없으므로 가비지이므로 수집 할 수 있습니다

그리고이 간단한 전략은 당신이 결정하는 문제를 정확히 가지고 있습니다 : 만약 A가 B를 참조하고 B가 B를 참조한다면, 그들의 참조 카운트는 모두 1보다 작을 수 없으며 , 이는 결코 수집되지 않을 것입니다.

이 문제를 처리하는 방법에는 네 가지가 있습니다.

  1. 무시해. 메모리가 충분하면주기가 작고 드물고 런타임이 짧을 경우주기를 수집하지 않고 벗어날 수 있습니다. 쉘 스크립트 인터프리터를 생각해보십시오. 쉘 스크립트는 일반적으로 몇 초 동안 만 실행되며 많은 메모리를 할당하지 않습니다.
  2. 사이클에 문제가없는 다른 가비지 수집기와 참조 횟수 가비지 수집기를 결합하십시오 . CPython은이를 수행합니다. 예를 들면 다음과 같습니다. CPython의 기본 가비지 수집기는 참조 횟수 수집기이지만 때때로주기를 수집하기 위해 추적 가비지 수집기가 실행됩니다.
  3. 주기를 감지하십시오. 불행하게도, 그래프에서 사이클을 감지하는 것은 다소 비용이 많이 드는 작업입니다. 특히, 추적 콜렉터와 거의 동일한 오버 헤드가 필요하므로 그 중 하나를 사용할 수도 있습니다.
  4. 순진한 방식으로 알고리즘을 구현하지 마십시오. 1970 년대 이래로 사이클 감지와 레퍼런스 카운팅을 단일 작업에서 영리한 방식으로 결합하여 수행하는 것보다 훨씬 저렴한 여러 흥미로운 알고리즘이 개발되었습니다. 별도로 또는 추적 수집기를 수행합니다.

그건 그렇고, 가비지 수집기를 구현하는 다른 주요 방법 (그리고 이미 위에서 몇 번 암시 했음)은 추적 입니다. 추적 수집기는 도달 가능성 개념을 기반으로합니다 . 당신은 몇 가지로 시작 루트 세트 당신이 알고 항상 도달 (전역 상수, 예를 들어, 또는 Object당신이 거기에서 클래스, 현재 어휘 범위, 현재 스택 프레임) 및 추적 루트 세트에서 연결할 수있는 모든 개체를 한 후, 전이 폐쇄가있을 때까지 루트 세트에서 도달 할 수있는 오브젝트 등에서 도달 할 수있는 모든 오브젝트. 폐쇄 되지 않은 모든 것은 쓰레기입니다.

주기는 자체 내에서만 도달 할 수 있지만 루트 세트에서는 도달 할 수 없으므로 수집됩니다.


1
질문은 Java에만 해당되므로 Java는 참조 계산을 사용하지 않으므로 문제가 존재하지 않는다고 언급 할 가치가 있다고 생각합니다. 또한 위키 백과에 대한 링크 는 "추가 읽기"로 도움이 될 것입니다. 그렇지 않으면 훌륭한 개요!
Alexander Malakhov

난 그냥 그래서 지금은 : 모르겠어요, 제리 관의 게시물에 대한 귀하의 의견을 읽었습니다
알렉산더 Malakhov

8

Java GC는 실제로 설명대로 작동하지 않습니다. "GC 루트"라고하는 기본 개체 집합에서 시작하여 루트에서 도달 할 수없는 개체를 수집한다고 말하는 것이 더 정확합니다.
GC 루트는 다음과 같은 것들을 포함합니다 :

  • 정적 변수
  • 현재 실행중인 스레드의 스택에있는 로컬 변수 (해당되는 모든 'this'참조 포함)

따라서 귀하의 경우, 지역 변수 a, b 및 c가 분석법 끝에서 범위를 벗어나면 3 개의 노드 중 하나에 대한 참조를 직접 또는 간접적으로 포함하는 더 이상 GC 루트가 없습니다. 가비지 수집 대상이됩니다.

TofuBeer의 링크는 원하는 경우 더 자세합니다.


"... 현재 실행중인 스레드 의 스택에 ..."다른 스레드의 데이터를 손상시키지 않기 위해 모든 스레드의 스택을 스캔하지 않습니까?
Alexander Malakhov

6

이 기사 (더 이상 사용할 수 없음)는 가비지 수집기에 대해 깊이있게 설명합니다 (개념적으로는 몇 가지 구현이 있습니다). 게시물의 관련 부분은 "A.3.4 도달 할 수 없음"입니다.

A.3.4 도달 할 수없는 개체 참조가 더 이상 존재하지 않으면 개체는 도달 할 수없는 상태가됩니다. 개체에 도달 할 수 없으면 수집 대상입니다. 문구에 유의하십시오 : 개체가 수집 대상이라고해서 개체가 즉시 수집되는 것은 아닙니다. JVM은 오브젝트가 소비하는 메모리가 즉시 필요할 때까지 콜렉션을 지연시킬 수 있습니다.



1
링크는 더 이상 사용할 수 없습니다
titus

1

가비지 콜렉션은 일반적으로 "다른 오브젝트가 해당 오브젝트를 가리키고 있지 않다면 일부 오브젝트를 정리합니다"를 의미하지 않습니다 (즉, 참조 횟수). 가비지 콜렉션은 대략 프로그램에서 도달 할 수없는 객체를 찾는 것을 의미합니다.

따라서 귀하의 예에서 a, b 및 c가 범위를 벗어난 후에는 더 이상 이러한 객체에 액세스 할 수 없으므로 GC에서 수집 할 수 있습니다.


"가비지 수집은 대략 프로그램에서 도달 할 수없는 개체를 찾는 것을 의미합니다." 대부분의 GC 알고리즘에서는 실제로 다른 방법입니다. GC 루트로 시작하여 찾을 수있는 것을 확인하면 나머지는 참조되지 않은 쓰레기로 간주됩니다.
Fredrik

1
참조 카운트 가비지 수집을위한 두 가지 주요 구현 전략 중 하나입니다. (다른 하나는 추적된다.)
르그 W MITTAG에게

3
@ Jörg : 오늘날 대부분의 사람들은 가비지 수집기에 대해 이야기 할 때 일종의 mark'n'sweep 알고리즘을 기반으로 수집기를 말합니다. 가비지 수집기가없는 경우 참조 횟수는 일반적으로 사용되는 문제입니다. 심판 계산은 어떤 의미에서 가비지 수집 전략이지만 오늘날 그 위에 세워진 gc는 거의 없기 때문에 실제로는 더 이상 gc가 아니기 때문에 gc 전략이라고하면 사람들을 혼란스럽게 할 것입니다 전략이지만 메모리를 관리하는 다른 방법입니다.
Fredrik

1

Bill은 질문에 직접 대답했습니다. Amnon이 말했듯이 가비지 수집에 대한 정의는 참조 횟수입니다. 마크 앤 스윕 및 카피 콜렉션과 같은 매우 간단한 알고리즘조차도 순환 참조를 쉽게 처리 할 수 ​​있다고 덧붙였습니다. 따라서 마법은 없습니다!

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