Java 객체가 더 이상 참조되지 않은 즉시 삭제되지 않는 이유는 무엇입니까?


77

Java에서 오브젝트에 더 이상 참조가 없으면 즉시 삭제할 수 있지만 JVM은 오브젝트가 실제로 삭제되는시기를 결정합니다. Objective-C 용어를 사용하기 위해 모든 Java 참조는 본질적으로 "강하다". 그러나 Objective-C에서 객체에 더 이상 강력한 참조가 없으면 객체가 즉시 삭제됩니다. Java에서 왜 그렇지 않습니까?


46
Java 객체가 실제로 삭제되는 시점 은 신경 쓰지 않아야합니다 . 구현 세부 사항입니다.
Basile Starynkevitch

154
@BasileStarynkevitch 시스템 / 플랫폼의 작동 방식에 전적으로 관심을 갖고 도전해야합니다. '어떻게'와 '왜'라는 질문을하는 것은 더 나은 프로그래머가되고 더 일반적으로 똑똑한 사람이되는 가장 좋은 방법 중 하나입니다.
Artur Biesiadowski

6
순환 참조가있을 때 목표 C는 무엇을 하는가? 나는 그것이 누출된다고 가정합니까?
Mehrdad

45
@ArturBiesiadowksi : 아니요, Java 사양은 객체가 삭제되는 시점 (및 R5RS의 경우 ) 을 알려주지 않습니다 . 삭제가 발생하지 않는 것처럼 Java 프로그램 개발할 수있을 것입니다 . Java hello world와 같은 수명이 짧은 프로세스의 경우 실제로 발생하지 않습니다. 다른 이야기 인 살아있는 물체 세트 (또는 메모리 소비)에 관심이있을 수 있습니다.
Basile Starynkevitch

28
어느 날 초보자는 마스터에게 "할당 문제에 대한 해결책이 있습니다. 모든 할당에 참조 카운트를 제공하고 0에 도달하면 객체를 삭제할 수 있습니다"라고 말했습니다. 마스터는 "어느 날 초보자가 마스터에게 말했다."해결책이 있습니다 ...
Eric Lippert

답변:


79

우선, Java에는 약한 참조와 소프트 참조라는 다른 최선의 노력 범주가 있습니다. 약한 참조와 강한 참조는 참조 계산과 가비지 수집과는 완전히 별개의 문제입니다.

둘째, 공간 사용을 줄임으로써 가비지 수집을보다 효율적으로 수행 할 수있는 메모리 사용 패턴이 있습니다. 예를 들어, 새로운 객체는 오래된 객체보다 삭제 될 가능성이 훨씬 높습니다. 따라서 스윕간에 약간의 대기 시간이있을 경우 소수의 생존자를 장기 스토리지로 이동시키면서 대부분의 새로운 메모리를 삭제할 수 있습니다. 장기 저장은 훨씬 덜 자주 스캔 할 수 있습니다. 수동 메모리 관리 또는 참조 카운팅을 통한 즉각적인 삭제는 조각화가 훨씬 쉽습니다.

그것은 월급 당 한 번 식료품 쇼핑을하는 것과 하루에 충분한 음식을 얻기 위해 매일가는 것의 차이와 같습니다. 한 번의 큰 여행은 개별적인 작은 여행보다 훨씬 오래 걸리지 만 전반적으로 시간과 돈을 절약하게됩니다.


58
프로그래머의 아내는 그를 슈퍼마켓으로 보낸다. 그녀는 그에게 "빵 한 덩어리를 사고, 달걀이 보이면 십여 개를 잡아라"고 말합니다. 프로그래머는 나중에 팔 아래 빵 12 개를 가지고 돌아옵니다.
Neil

7
새로운 세대의 gc 시간은 일반적으로 실제 객체의 양에 비례 하므로 더 많은 객체를 삭제하면 비용이 전혀 지불되지 않습니다. 삭제는 생존자 공간 포인터를 뒤집고 선택적으로 하나의 큰 memset에서 전체 메모리 공간을 0으로 만드는 것만 큼 간단합니다 (현재 jvm에서 tlab 또는 객체 자체를 할당하는 동안 gc의 끝에서 수행되거나 상실되는 지 확실하지 않음)
Artur Biesiadowski

64
@ 닐은 13 개의 빵이되어서는 안됩니까?
JAD

67
"통로 7에 하나의 오류로 인해 꺼짐"
joeytwiddle

13
@ JAD 나는 13을 말했을 것이지만, 대부분은 그것을 얻지 않는 경향이 있습니다. ;)
Neil

86

더 이상 참조되지 않는 것을 올바르게 아는 것은 쉽지 않습니다. 쉽게 접근 할 수 없습니다.

서로 참조하는 두 개의 객체가있는 경우 어떻게합니까? 그들은 영원히 남아 있습니까? 이러한 사고 방식을 확장하여 임의의 데이터 구조를 해결하면 JVM 또는 기타 가비지 수집기가 왜 여전히 필요한지, 무엇을 할 수 있는지 결정하는 훨씬 더 정교한 방법을 사용해야하는 이유를 곧 알게 될 것입니다.


7
또는 순환 종속성 종속성 메모리가있을 것으로 예상 할 때 GC를 사용하여 가능한 한 재검토를 사용하는 Python 접근 방식을 취할 수 있습니다. 왜 그들이 GC 외에도 재검토를 할 수 없었는지 모르겠습니까?
Mehrdad

27
@Mehrdad 그들은 할 수 있었다. 그러나 아마도 느릴 것입니다. 아무것도 구현하지 못하지만 Hotspot 또는 OpenJ9의 GC를 이길 것으로 기대하지 마십시오.
Josef

21
@ jpmc26 객체를 더 이상 사용하지 않는 즉시 삭제하면 부하가 높은 상황에서 객체가 삭제 될 가능성이 높기 때문에로드가 훨씬 증가합니다. 부하가 적을 때 GC를 실행할 수 있습니다. 참조 카운트 자체는 모든 참조에 대해 작은 오버 헤드입니다. 또한 GC를 사용하면 단일 객체를 처리하지 않고도 참조가없는 많은 양의 메모리를 버릴 수 있습니다.
Josef

33
@Josef : 적절한 참조 카운팅도 무료가 아닙니다. 참조 카운트 업데이트에는 원자 단위 증분 / 감소가 필요하며 , 특히 최신 멀티 코어 아키텍처에서 비용이 많이 듭니다 . CPython에서는 큰 문제가되지 않습니다 (CPython은 자체적으로 매우 느리고 GIL은 멀티 스레드 성능을 단일 코어 수준으로 제한합니다). 그러나 병렬 처리를 지원하는 더 빠른 언어에서는 문제가 될 수 있습니다. PyPy가 참조 카운팅을 완전히 제거하고 GC를 사용할 가능성은 없습니다.
Matteo Italia 12

10
@Mehrdad Java를위한 참조 카운팅 GC를 구현 한 후에는 다른 GC 구현보다 성능이 나쁜 경우를 찾기 위해 기꺼이 테스트하겠습니다.
Josef

45

AFAIK, JVM 사양 (영어로 작성 됨)은 정확히 객체 (또는 값)를 삭제해야하는 시점을 언급하지 않으며 구현에 그대로 둡니다 ( R5RS의 경우 ). 어떻게 든 가비지 수집기를 요구하거나 제안 하지만 세부 사항을 구현에 남겨 둡니다. 마찬가지로 Java 사양에서도 마찬가지입니다.

기억 프로그래밍 언어 입니다 사양 (의 구문 , 의미 가 아니라 소프트웨어 구현, 등 ...). Java (또는 JVM)와 같은 언어에는 많은 구현이 있습니다. 사양은 출판되고 , 다운로드 가능하며 (연구 할 수 있도록) 영어로 작성됩니다. §2.5.3 JVM 사양의 에 가비지 수집기가 언급되어 있습니다.

오브젝트의 힙 스토리지는 자동 스토리지 관리 시스템 (가비지 콜렉터)에 의해 재생됩니다. 객체는 명시 적으로 할당 해제되지 않습니다. Java Virtual Machine은 특정 유형의 자동 스토리지 관리 시스템을 가정 하지 않습니다.

(강조는 내 것입니다; BTW 마무리는 Java 사양의 §12.6 에, 메모리 모델은 Java 사양의 §17.4 에 있습니다)

그래서 (자바) 당신은 상관하지 말아야 할 때 개체가 삭제됩니다 , 당신은 코드 수 로-경우 (AN에서 추론하여이 발생하지 않습니다 추상화 는 것을 무시하는 경우). 물론 메모리 소비와 살아있는 물체 세트에 관심을 가져야합니다. 이는 다른 질문입니다. 몇 가지 간단한 경우 ( "hello world"프로그램을 생각하면) 할당 된 메모리가 약간 작다는 것을 증명하거나 스스로 확신 할 수 있습니다 (예 : 기가 바이트 미만). 개별 객체 삭제 . 더 많은 경우에는 살아있는 물체가(또는 살아있는 것들에 대한 추론하기 쉬운) 도달 가능한 것들은 합리적인 한계를 초과하지 않으며 (GC에 의존하지만 가비지 수집이 언제 어떻게 발생하는지는 신경 쓰지 않습니다). 공간 복잡성 에 대해 읽어보십시오 .

헬로우 월드와 같은 짧은 Java 프로그램을 실행하는 여러 JVM 구현에서 가비지 수집기가 전혀 트리거되지 않으며 삭제가 발생하지 않습니다. AFAIU, 이러한 행동은 수많은 Java 사양을 준수합니다.

대부분의 JVM 구현은 세대 별 복사 기술을 사용합니다 (적어도 대부분의 Java 객체, 종료 또는 약한 참조를 사용하지 않는 것) . 종료는 짧은 시간 내에 발생한다고 보장 할 수 없으며 연기 될 수 있으므로 코드가 유용하지 않은 유용한 기능 일뿐입니다. 개별 객체를 삭제한다는 개념은 의미가 없습니다 (많은 객체에 대한 메모리 영역을 포함하는 큰 블록의 블록, 아마도 한 번에 몇 메가 바이트이므로 한 번에 해제되기 때문에).

JVM 사양에서 각 객체를 가능한 한 빨리 삭제해야하거나 (또는 ​​객체 삭제에 더 많은 제약을 가할 필요가있는 경우) 효율적인 GC 기술은 금지되어 있으며 Java 및 JVM 설계자는이를 피하는 것이 현명합니다.

BTW, 객체를 삭제하지 않고 메모리를 해제하지 않는 순진한 JVM이 사양 (정신이 아닌 문자)을 준수하고 실제로 실제로 세계를 실행할 수 있습니다 (대부분의 작고 짧은 수명의 Java 프로그램은 아마도 몇 기가 바이트 이상의 메모리를 할당하지 않을 것입니다). 물론 그러한 JVM은 언급 할 가치가 없으며 장난감 일뿐입니다 ( C 의이 구현 과 같습니다 malloc). 자세한 내용은 Epsilon NoOp GC 를 참조하십시오. 실제 JVM은 매우 복잡한 소프트웨어이며 여러 가비지 수집 기술을 혼합합니다.

또한 Java 는 JVM과 동일하지 않으며 JVM없이 실행되는 Java 구현이 있습니다 (예 : 사전 Java 컴파일러, Android 런타임 ). 에서 일부 의 경우 (주로 학술 것), 당신은 때문에 자바 프로그램을 할당하거나 런타임 (예에서 삭제하지 않는 것이 (그래서 "컴파일 타임 가비지 컬렉션"기술이라고도 함) 상상 최적화 컴파일러는 단지를 사용하는만큼 영리하고있다 호출 스택자동 변수 ).

Java 객체가 더 이상 참조되지 않은 즉시 삭제되지 않는 이유는 무엇입니까?

Java 및 JVM 사양에는 필요하지 않기 때문입니다.


자세한 내용은 GC 핸드북 (및 JVM 사양 )을 참조하십시오. 객체에 대해 살아 있거나 (나중에 계산할 때 유용함) 전체 프로그램 (비 모듈 식) 속성입니다.

Objective-C메모리 관리에 대한 참조 계산 방식을 선호합니다 . 또한 함정을 가지고 (예 : 목표 - C의 프로그래머가 걱정하는 순환 참조 약한 참조를 expliciting에 의해,하지만 JVM은 자바 프로그래머의 관심을 요구하지 않고 실제로 잘 순환 참조를 처리합니다).

프로그래밍 및 프로그래밍 언어 디자인 에는 Silver Bullet없습니다 ( Halting Problem유의하십시오 ; 유용한 살아있는 대상이되는 것은 일반적으로 결정할 수 없습니다 ).

SICP , Programming Language Pragmatics , Dragon Book , Lisp in Small Pieces and Operating Systems : Three Easy Pieces를 읽을 수도 있습니다 . 그것들은 자바에 관한 것이 아니라, 당신의 마음을 열고 JVM이해야 할 일과 그것이 실제로 컴퓨터에서 어떻게 작동하는지 이해하는 데 도움이 될 것입니다. 또한 기존 오픈 소스 JVM 구현 의 복잡한 소스 코드 ( 수백만 개의 소스 코드 라인이있는 OpenJDK 등 )를 연구하는 데 몇 달 (또는 몇 년)을 소비 할 수 있습니다 .


20
"객체를 삭제하지 않고 메모리를 해제하지 않는 순진한 JVM이 스펙을 준수 할 수 있습니다."스펙을 준수하는 것이 가장 확실합니다! Java 11은 실제로 매우 짧은 프로그램을 위해 no-op 가비지 수집기추가하고 있습니다.
Michael

6
"개체가 삭제 될 때 신경 쓰지 않아야합니다" 우선 RAII는 더 이상 실현 가능한 패턴이 아니며 finalize리소스 관리 (파일 핸들, db 연결, GPU 리소스 등)에 의존 할 수 없다는 것을 알아야합니다 .
Alexander

4
@Michael 사용한 메모리 한도를 사용하여 일괄 처리에 적합합니다. OS는 "이 프로그램에서 사용하는 모든 메모리가 사라졌습니다!"라고 말할 수 있습니다. 결국, 그것은 다소 빠릅니다. 실제로 C의 많은 프로그램은 특히 초기 유닉스 세계에서 그런 식으로 작성되었습니다. 파스칼은 매우 안전하지는 않았지만, 표시, 하위 작업 시작, 재설정과 같이 상당히 똑같은 일을 할 수있게 해주는 "끔찍하게 스택 / 힙 포인터를 미리 저장된 체크 포인트로 재설정"했습니다.
Luaan

6
@Alexander는 일반적으로 C ++ 외부 (및 의도적으로 파생 된 몇 가지 언어)에서 RAII가 종료 자만을 기반으로 작동한다고 가정하면 반 패턴이며, 명시적인 리소스 제어 블록으로 경고하고 대체해야합니다. GC의 요점은 결국 수명과 자원이 분리된다는 것입니다.
Leushenko

3
@Leushenko 저는 "생명과 자원이 분리되어있다"는 것이 GC의 "완전한 포인트"라는 점에 동의하지 않습니다. 쉽고 안전한 메모리 관리 : GC의 주요 요점에 대해 지불하는 부정적인 가격입니다. "RAII가 파이널 라이저만으로 작동한다고 가정하면 안티 패턴"이라고 Java에서? 혹시. 그러나 CPython, Rust, Swift 또는 Objective C에서는 "명시 적 자원 제어 블록에 대해 경고하고이를 대체"하지 않았습니다. RAII를 통해 자원을 관리하는 오브젝트는 범위가 지정된 수명을 넘어갈 수있는 핸들을 제공합니다. try-with-resource 블록은 단일 범위로 제한됩니다.
Alexander

23

Objective-C 용어를 사용하기 위해 모든 Java 참조는 본질적으로 "강하다".

정확하지 않습니다. Java에는 약한 참조와 약한 참조가 모두 있지만 언어 키워드가 아닌 객체 수준에서 구현됩니다.

Objective-C에서 객체에 더 이상 강한 참조가 없으면 객체가 즉시 삭제됩니다.

Objective C의 일부 버전은 실제로 세대 가비지 수집기를 사용했습니다. 다른 버전에는 가비지 수집이 전혀 없었습니다.

Objective C의 최신 버전은 추적 기반 GC 대신 ARC (Automatic Reference Counting)를 사용하는 것이 사실이며, 이로 인해 참조 횟수가 0에 도달 할 때 오브젝트가 "삭제"됩니다. 그러나 JVM 구현도 준수 할 수 있으며 이런 방식으로 정확하게 작동 할 수 있습니다 (호환 될 수 있으며 GC가 전혀 없음).

그렇다면 대부분의 JVM 구현이 왜 그렇게하지 않고 대신 추적 기반 GC 알고리즘을 사용합니까?

간단히 말해서, ARC는 처음 보이는 것처럼 유토피아 적이 지 않습니다.

  • 참조가 복사, 수정 또는 범위를 벗어날 때마다 카운터를 늘리거나 줄여야하기 때문에 성능상 명백한 오버 헤드가 발생합니다.
  • ARC는 순환 참조를 쉽게 지울 수 없습니다. 순환 참조는 모두 서로에 대한 참조가 있으므로 참조 횟수가 0이되지 않습니다.

ARC는 물론 장점이 있습니다. 구현과 수집이 간단하여 결정적입니다. 그러나 위의 단점은 대부분의 JVM 구현이 세대에 따라 추적 기반 GC를 사용하는 이유입니다.


1
재미있는 점은 애플이 실제로 다른 GC (특히 세대의 GC)를 능가하는 것을 보았 기 때문에 ARC로 정확하게 전환했다는 것입니다. 공평하게 말하면, 이것은 대부분 메모리 제한 플랫폼 (iPhone)에서 그렇습니다. 그러나 나는 세대 적 (및 다른 비결정론 적) GC가 처음 보이는 것처럼 유토피아 적이 지 않다고 말함으로써“ARC는 처음 보이는 것만 큼 유토피아 적이 지 않다”는 당신의 진술에 반박 할 것입니다. 결정 론적 파괴는 아마도 대부분의 시나리오.
Konrad Rudolph

3
@KonradRudolph 나는 결정 론적 파괴의 팬이기도하지만“대부분의 시나리오에서 더 나은 옵션”이 유지되지는 않는다고 생각합니다. 지연 시간이나 메모리가 평균 처리량보다 중요한 경우, 특히 논리가 상당히 간단한 경우에는 더 나은 옵션입니다. 그러나 많은 순환 참조 등이 필요하고 빠른 평균 작동이 필요한 복잡한 응용 프로그램이 많지 않지만 실제로 대기 시간에 신경 쓰지 않고 사용 가능한 메모리가 많은 것은 아닙니다. 이를 위해 ARC가 좋은 아이디어인지 의심됩니다.
leftaroundabout

1
@leftaroundabout“대부분의 시나리오”에서 처리량이나 메모리 압력은 병목 현상이 아니므로 어느 쪽이든 중요하지 않습니다. 귀하의 예는 하나의 특정 시나리오입니다. 물론, 매우 드문 일이 아니지만 ARC가 더 적합한 다른 시나리오보다 더 일반적이라고 주장하지는 않습니다. 또한 ARC 사이클을 잘 처리 할 수 있습니다 . 프로그래머의 간단한 수동 개입이 필요합니다. 이것은 덜 이상적이지만 거래 차단기는 거의 없습니다. 나는 결정 론적 마무리가 당신이 주장하는 것보다 훨씬 더 중요한 특징이라고 주장한다.
Konrad Rudolph

3
@KonradRudolph ARC가 프로그래머의 간단한 수동 개입이 필요한 경우 사이클을 처리하지 않습니다. 이중 연결 목록을 많이 사용하기 시작하면 ARC는 수동 메모리 할당으로 바뀝니다. 큰 임의의 그래프가있는 경우 ARC는 강제로 가비지 수집기를 작성합니다. GC의 주장은 파괴가 필요한 자원은 메모리 서브 시스템의 역할이 아니며 상대적으로 적은 수의 자원을 추적하기 위해서는 프로그래머가 간단한 수동 개입을 통해 결정적으로 마무리해야한다는 것이다.
prosfilaes

2
@KonradRudolph ARC 및주기는 기본적으로 수동으로 처리하지 않으면 메모리 누수로 이어집니다. 복잡한 시스템에서는 맵에 저장된 일부 객체가 해당 맵에 대한 참조, 해당 맵을 생성 및 파괴하는 코드 섹션을 담당하지 않는 프로그래머가 수행 할 수있는 변경 사항을 저장하는 경우 주요 누수가 발생할 수 있습니다. 큰 임의의 그래프는 내부 ​​포인터가 강하지 않다는 것을 의미하지는 않으며 연결된 항목이 사라지는 것은 괜찮습니다. 메모리 누수를 처리하는 것이 수동으로 파일을 닫는 것보다 문제가 적은지 여부는 말할 수 없지만 실제로는 아닙니다.
prosfilaes

5

Java는 객체가 수집되는 시점을 정확하게 지정하지 않으므로 구현에서 가비지 수집을 처리하는 방법을 자유롭게 선택할 수 있습니다.

가비지 수집 메커니즘에는 여러 가지가 있지만 객체를 즉시 수집 할 수 있도록 보장하는 메커니즘은 거의 대부분 참조 카운팅을 기반으로합니다 (이 추세를 깨뜨리는 알고리즘은 모릅니다). 참조 카운트는 강력한 도구이지만 참조 카운트를 유지하는 비용이 듭니다. 단일 스레드 코드에서는 증가 및 감소에 지나지 않으므로 포인터를 할당하면 참조 카운트가 아닌 코드에서보다 참조 카운트 코드에서 3 배 정도의 비용이들 수 있습니다 (컴파일러가 모든 것을 머신으로 구울 수있는 경우) 암호).

멀티 스레드 코드에서는 비용이 더 높습니다. 원자 적 증가 / 감소 또는 잠금을 요구하며 둘 다 비쌀 수 있습니다. 최신 프로세서에서 원 자성 연산은 간단한 레지스터 연산보다 20 배 정도 비쌉니다 (프로세서마다 다름). 이것은 비용을 증가시킬 수 있습니다.

이를 통해 여러 모델의 장단점을 고려할 수 있습니다.

  • Objective-C는 ARC (자동 참조 계산)에 중점을 둡니다. 그들의 접근 방식은 모든 것에 참조 카운팅을 사용하는 것입니다. 사이클 감지 기능이 없기 때문에 (알고있는) 프로그래머는 사이클이 발생하는 것을 막을 것으로 예상되므로 개발 시간이 걸립니다. 그들의 이론은 포인터에 자주 할당되는 것은 아니며, 컴파일러는 참조 카운트 증가 / 감소로 인해 객체가 죽을 수없는 상황을 식별하고 그 증가 / 감소를 완전히 제거 할 수 있다는 것입니다. 따라서 참조 계산 비용을 최소화합니다.

  • CPython은 하이브리드 메커니즘을 사용합니다. 참조 횟수를 사용하지만주기를 식별하고 해제하는 가비지 수집기도 있습니다. 이는 두 가지 접근 방식을 모두 희생하면서 두 세계의 이점을 제공합니다. CPython은 참조 횟수 주기를 감지하기 위해 책을 보관하십시오. CPython은 두 가지 방법으로이 문제를 해결합니다. CPython은 실제로 완전히 멀티 스레드되지는 않습니다. 멀티 스레딩을 제한하는 GIL이라는 잠금 장치가 있습니다. 이는 CPython이 원자 단위가 아닌 일반 증분 / 감소를 사용할 수 있다는 것을 의미합니다. CPython도 해석됩니다. 즉, 변수에 할당하는 것과 같은 연산은 이미 1이 아닌 소수의 명령어를 사용합니다. C 코드에서 빠르게 수행되는 증분 / 감소를 수행하는 추가 비용은 문제가되지 않습니다. 이미이 비용을 지불했습니다.

  • Java는 참조 카운트 시스템을 전혀 보장하지 않는 접근 방식을 취합니다. 실제로 스펙은 말을하지 않는 어떤 객체가 자동 스토리지 관리 시스템이 될 것보다는 다른 관리 방법에 대한합니다. 그러나이 사양은 사이클을 처리하는 방식으로 가비지 수집 될 것이라는 가정을 강력히 암시합니다. 객체가 만료되는시기를 지정하지 않으면 java는 시간 증가 / 감소를 낭비하지 않는 수집기를 자유롭게 사용할 수 있습니다. 실제로 세대 별 가비지 수집기와 같은 영리한 알고리즘은 회수되는 데이터를 보지 않고도 많은 간단한 경우를 처리 할 수 ​​있습니다 (아직 참조되는 데이터 만보아야 함).

그래서 우리는이 세 가지 각각이 절충을해야한다는 것을 알 수 있습니다. 어떤 트레이드 오프가 가장 좋은지는 언어가 사용되는 방식의 본질에 크게 좌우됩니다.


4

finalizeJava의 GC에 대해 피기 백을 받았지만 핵심 가비지 콜렉션은 죽은 오브젝트가 아니라 살아있는 오브젝트에 관심이 있습니다. 일부 GC 시스템에서 (일부 Java 구현을 포함 할 수 있음), 어떤 용도로 사용되지 않는 스토리지와 객체를 나타내는 비트를 구별하는 유일한 것은 전자에 대한 참조가있을 수 있습니다. 종료자가있는 개체는 특수 목록에 추가되지만 다른 개체에는 유니버스의 저장소가 사용자 코드에 포함 된 참조를 제외하고 개체와 연결되어 있다고 말하는 것이 없습니다. 마지막으로 이러한 참조를 덮어 쓰면 메모리의 비트 패턴 은 유니버스의 어떤 것도 인식하는지 여부에 관계없이 즉시 개체로 인식 할 수 없게됩니다.

가비지 수집의 목적은 참조가없는 객체를 파괴하는 것이 아니라 세 가지를 달성하는 것입니다.

  1. 도달 가능한 참조가없는 개체를 식별하는 약한 참조를 무효화합니다.

  2. 파이널 라이저를 사용하여 시스템의 오브젝트 목록을 검색하여 도달 가능한 참조가없는 오브젝트가 있는지 확인하십시오.

  3. 객체가 사용하지 않는 스토리지 영역을 식별하고 통합합니다.

GC의 주요 목표는 # 3이며,이를 수행하기 전에 대기 시간이 길어질수록 통합 기회가 더 많아 질 수 있습니다. 스토리지를 즉시 사용하는 경우에는 # 3을 수행하는 것이 합리적이지만 그렇지 않으면 연기하는 것이 더 합리적입니다.


5
실제로 gc는 무한 메모리 시뮬레이션이라는 목표를 가지고 있습니다. 목표로 지정한 모든 것은 추상화의 불완전 성 또는 구현 세부 사항입니다.
중복 제거기

@ 중복 제거기 : 약한 참조는 GC 지원 없이는 얻을 수없는 유용한 의미를 제공합니다.
supercat

물론 약한 참조에는 유용한 의미가 있습니다. 그러나 시뮬레이션이 더 좋으면 이러한 의미론이 필요할까요?
중복 제거기

@ 중복 제거기 : 예. 업데이트가 열거와 상호 작용하는 방법을 정의하는 컬렉션을 고려하십시오. 이러한 컬렉션은 라이브 열거자를 약하게 참조해야 할 수 있습니다. 무제한 메모리 시스템에서 반복적으로 반복되는 컬렉션은 관심있는 열거 자 목록이 제한없이 커집니다. 해당 목록에 필요한 메모리는 문제가되지 않지만 반복하는 데 필요한 시간은 시스템 성능을 저하시킵니다. GC를 추가하면 O (N)과 O (N ^ 2) 알고리즘의 차이를 의미 할 수 있습니다.
supercat

2
왜리스트에 추가하고 그들이 사용될 때 스스로를 찾도록하는 대신 열거 자에게 알리고 싶습니까? 그리고 메모리 압력에 의존하는 대신 적시에 처리되는 쓰레기에 의존하는 프로그램은 전혀 움직이지 않는다면 죄의 상태에 살고 있습니다.
중복 제거기

4

귀하의 질문에 대해 다시 말하고 일반화를 제안하겠습니다.

Java가 GC 프로세스를 강력하게 보장하지 않는 이유는 무엇입니까?

이를 염두에두고 여기에서 답변을 빠르게 스크롤하십시오. 지금까지 7 개 (이것은 포함하지 않음)가 있으며 주석 스레드는 상당히 많습니다.

그게 당신의 대답입니다.

GC는 어렵다. 많은 고려 사항, 많은 다른 트레이드 오프 및 궁극적으로 매우 다른 접근 방식이 있습니다. 이러한 접근 방식 중 일부는 GC가 필요하지 않은 즉시 객체를 실현할 수있게합니다. 다른 사람들은 그렇지 않습니다. 계약을 느슨하게 유지함으로써 Java는 구현 자에게 더 많은 옵션을 제공합니다.

물론이 결정에도 트레이드 오프가 있습니다. 계약을 느슨하게 유지함으로써 Java는 대부분 프로그래머가 소멸자에 의존 할 수있는 능력을 제거합니다. 이것은 C ++ 프로그래머들이 종종 ([citation needed];)) 그리워하는 것이므로 그다지 중요하지 않은 트레이드 오프는 아닙니다. 필자는 특정 메타 결정에 대한 논의를 보지 못했지만 Java 사람들은 아마도 더 많은 GC 옵션을 사용함으로써 얻을 수있는 이점이 객체가 언제 파괴 될 것인지 프로그래머에게 알릴 수있는 이점보다 중요하다고 결정했을 것입니다.


* finalize방법이 있지만이 답변의 범위를 벗어난 여러 가지 이유 때문에이 방법에 의존하는 것은 어렵고 좋은 생각이 아닙니다.


3

개발자가 작성한 명시 적 코드없이 메모리를 처리하는 두 가지 전략이 있습니다. 가비지 수집 및 참조 횟수.

가비지 콜렉션은 개발자가 어리석은 짓을하지 않는 한 "작동"한다는 장점이 있습니다. 참조 횟수를 사용하면 참조주기를 가질 수 있습니다. 즉, "작동"하지만 개발자가 영리해야합니다. 따라서 가비지 수집에 도움이됩니다.

참조 횟수를 사용하면 참조 횟수가 0으로 떨어지면 개체가 즉시 사라집니다. 이는 참조 카운팅의 이점입니다.

가비지 수집의 팬을 믿으면 가비지 수집이 더 빠르며, 참조 수의 팬을 믿으면 가비지 수집이 더 빠릅니다.

동일한 목표를 달성하는 것은 두 가지 다른 방법입니다 .Java는 하나의 방법을 선택했으며 Objective-C는 다른 방법을 선택했습니다.

가비지 수집에서 참조 계산으로 Java를 변경하는 것은 많은 코드 변경이 필요하기 때문에 중요한 작업입니다.

이론적으로 Java는 가비지 수집과 참조 계산을 혼합하여 구현할 수 있습니다. 참조 수가 0이면 객체에 도달 할 수 없지만 반드시 다른 방법은 아닙니다. 그래서 당신은 할 수 참조 카운트를 유지하고 삭제할 객체를 자신의 참조 횟수가 0 인 경우 (다음에 연결할 참조 사이클 내에서 객체를 잡을 수시로 가비지 컬렉션을 실행). 가비지 수집에 참조 계산을 추가하는 것이 좋지 않다고 생각하는 사람들과 가비지 수집을 참조 계산에 추가하는 것이 나쁜 생각이라고 생각하는 사람들은 세계가 50/50으로 나뉘어져 있다고 생각합니다. 그래서 이것은 일어나지 않을 것입니다.

따라서 Java 참조 수가 0이되면 즉시 오브젝트를 삭제하고 나중에 도달 할 수없는주기 내에 오브젝트를 삭제할 수 있습니다. 그러나 이것은 디자인 결정이며 Java는 그 결정에 반대했습니다.


참조 카운트를 사용하면 프로그래머가 사이클을 처리하므로 마무리 작업이 쉽지 않습니다. gc를 사용하면 사이클이 사소하지만 프로그래머는 마무리에주의해야합니다.
중복 제거기

@Deduplicator Java에서는 최종 객체에 대한 강력한 참조를 생성 할 수도 있습니다. Objective-C 및 Swift에서 참조 횟수가 0이면 객체 사라집니다 (무한한 루프를 할당 해제 / 삭제하지 않는 한).
gnasher729

deinit를 deist로 바꾸는 어리석은 철자법 검사기…
gnasher729

1
대부분의 프로그래머가 자동 철자 수정을 싫어하는 이유가 있습니다 ... ;-)
중복 제거기

lol ... 가비지 수집에 참조 계산을 추가하는 것이 좋지 않다고 생각하는 사람들과 가비지 수집을 참조 계산에 추가하는 것이 좋지 않다고 생각하는 사람들과 세상이 0.1 / 0.1 / 99.8로 나뉘어져 있다고 생각합니다. 톤이 이미 다시 냄새를
맡기

1

더 이상 객체에 대한 참조가 없을 때 이해의 어려움에 대한 다른 모든 성능 주장과 토론은 정확하지만 언급 할 가치가 있다고 생각하는 다른 아이디어는 이와 같은 것을 고려하는 적어도 하나의 JVM (azul)이 있다는 것입니다 그것은 본질적으로 vm 스레드가 지속적으로 참조를 검사하여 참조를 삭제하려고 시도하는 병렬 gc를 구현한다는 것입니다. 기본적으로 힙을 계속 둘러보고 참조되지 않은 메모리를 회수하려고 시도합니다. 이로 인해 약간의 성능 비용이 발생하지만 기본적으로 GC 시간이 0이거나 매우 짧습니다. (이는 끊임없이 확장되는 힙 크기가 시스템 RAM을 초과하지 않으면 Azul이 혼란스러워하고 용이없는 경우가 아닙니다)

TLDR JVM에는 이런 종류의 것이 존재합니다. 그것은 단지 특별한 jvm이며 다른 엔지니어링 손상과 같은 단점이 있습니다.

면책 조항 : 나는 Azul과 아무런 관련이 없으며 우리는 이전 직장에서 그것을 사용했습니다.


1

지속적인 처리량을 최대화하거나 gc 대기 시간을 최소화하는 것은 동적 긴장 상태에 있으며, 이는 아마도 GC가 즉시 발생하지 않는 가장 일반적인 이유 일 것입니다. 911 긴급 앱과 같은 일부 시스템에서는 특정 대기 시간 임계 값을 충족하지 않으면 사이트 장애 조치 프로세스가 트리거 될 수 있습니다. 은행 및 / 또는 차익 거래 사이트와 같은 다른 경우에는 처리량을 최대화하는 것이 훨씬 더 중요합니다.


0

속도

이 모든 일이 진행되는 이유는 궁극적으로 속도 때문입니다. 프로세서가 무한히 빠르거나 실용적 이도록 (예 : 초당 1,000,000,000,000,000,000,000,000,000,000,000) 작업을 수행하면 역 참조 된 객체가 삭제되도록하는 등 각 운영자간에 미친 길고 복잡한 일이 발생할 수 있습니다. 초당 해당 작업 수가 사실이 아니며 대부분의 다른 답변에서 설명하는 것처럼 실제로 복잡하고 리소스를 많이 사용하므로 가비지 수집이 존재하므로 프로그램이 실제로 달성하려는 작업에 집중할 수 있습니다. 빠른 방법.


글쎄, 우리는 그것보다 여분의 사이클을 사용하는 더 흥미로운 방법을 찾을 것이라고 확신합니다.
중복 제거기
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.