Java에서 오브젝트에 더 이상 참조가 없으면 즉시 삭제할 수 있지만 JVM은 오브젝트가 실제로 삭제되는시기를 결정합니다. Objective-C 용어를 사용하기 위해 모든 Java 참조는 본질적으로 "강하다". 그러나 Objective-C에서 객체에 더 이상 강력한 참조가 없으면 객체가 즉시 삭제됩니다. Java에서 왜 그렇지 않습니까?
Java에서 오브젝트에 더 이상 참조가 없으면 즉시 삭제할 수 있지만 JVM은 오브젝트가 실제로 삭제되는시기를 결정합니다. Objective-C 용어를 사용하기 위해 모든 Java 참조는 본질적으로 "강하다". 그러나 Objective-C에서 객체에 더 이상 강력한 참조가 없으면 객체가 즉시 삭제됩니다. Java에서 왜 그렇지 않습니까?
답변:
우선, Java에는 약한 참조와 소프트 참조라는 다른 최선의 노력 범주가 있습니다. 약한 참조와 강한 참조는 참조 계산과 가비지 수집과는 완전히 별개의 문제입니다.
둘째, 공간 사용을 줄임으로써 가비지 수집을보다 효율적으로 수행 할 수있는 메모리 사용 패턴이 있습니다. 예를 들어, 새로운 객체는 오래된 객체보다 삭제 될 가능성이 훨씬 높습니다. 따라서 스윕간에 약간의 대기 시간이있을 경우 소수의 생존자를 장기 스토리지로 이동시키면서 대부분의 새로운 메모리를 삭제할 수 있습니다. 장기 저장은 훨씬 덜 자주 스캔 할 수 있습니다. 수동 메모리 관리 또는 참조 카운팅을 통한 즉각적인 삭제는 조각화가 훨씬 쉽습니다.
그것은 월급 당 한 번 식료품 쇼핑을하는 것과 하루에 충분한 음식을 얻기 위해 매일가는 것의 차이와 같습니다. 한 번의 큰 여행은 개별적인 작은 여행보다 훨씬 오래 걸리지 만 전반적으로 시간과 돈을 절약하게됩니다.
더 이상 참조되지 않는 것을 올바르게 아는 것은 쉽지 않습니다. 쉽게 접근 할 수 없습니다.
서로 참조하는 두 개의 객체가있는 경우 어떻게합니까? 그들은 영원히 남아 있습니까? 이러한 사고 방식을 확장하여 임의의 데이터 구조를 해결하면 JVM 또는 기타 가비지 수집기가 왜 여전히 필요한지, 무엇을 할 수 있는지 결정하는 훨씬 더 정교한 방법을 사용해야하는 이유를 곧 알게 될 것입니다.
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 등 )를 연구하는 데 몇 달 (또는 몇 년)을 소비 할 수 있습니다 .
finalize
리소스 관리 (파일 핸들, db 연결, GPU 리소스 등)에 의존 할 수 없다는 것을 알아야합니다 .
Objective-C 용어를 사용하기 위해 모든 Java 참조는 본질적으로 "강하다".
정확하지 않습니다. Java에는 약한 참조와 약한 참조가 모두 있지만 언어 키워드가 아닌 객체 수준에서 구현됩니다.
Objective-C에서 객체에 더 이상 강한 참조가 없으면 객체가 즉시 삭제됩니다.
Objective C의 일부 버전은 실제로 세대 가비지 수집기를 사용했습니다. 다른 버전에는 가비지 수집이 전혀 없었습니다.
Objective C의 최신 버전은 추적 기반 GC 대신 ARC (Automatic Reference Counting)를 사용하는 것이 사실이며, 이로 인해 참조 횟수가 0에 도달 할 때 오브젝트가 "삭제"됩니다. 그러나 JVM 구현도 준수 할 수 있으며 이런 방식으로 정확하게 작동 할 수 있습니다 (호환 될 수 있으며 GC가 전혀 없음).
그렇다면 대부분의 JVM 구현이 왜 그렇게하지 않고 대신 추적 기반 GC 알고리즘을 사용합니까?
간단히 말해서, ARC는 처음 보이는 것처럼 유토피아 적이 지 않습니다.
ARC는 물론 장점이 있습니다. 구현과 수집이 간단하여 결정적입니다. 그러나 위의 단점은 대부분의 JVM 구현이 세대에 따라 추적 기반 GC를 사용하는 이유입니다.
Java는 객체가 수집되는 시점을 정확하게 지정하지 않으므로 구현에서 가비지 수집을 처리하는 방법을 자유롭게 선택할 수 있습니다.
가비지 수집 메커니즘에는 여러 가지가 있지만 객체를 즉시 수집 할 수 있도록 보장하는 메커니즘은 거의 대부분 참조 카운팅을 기반으로합니다 (이 추세를 깨뜨리는 알고리즘은 모릅니다). 참조 카운트는 강력한 도구이지만 참조 카운트를 유지하는 비용이 듭니다. 단일 스레드 코드에서는 증가 및 감소에 지나지 않으므로 포인터를 할당하면 참조 카운트가 아닌 코드에서보다 참조 카운트 코드에서 3 배 정도의 비용이들 수 있습니다 (컴파일러가 모든 것을 머신으로 구울 수있는 경우) 암호).
멀티 스레드 코드에서는 비용이 더 높습니다. 원자 적 증가 / 감소 또는 잠금을 요구하며 둘 다 비쌀 수 있습니다. 최신 프로세서에서 원 자성 연산은 간단한 레지스터 연산보다 20 배 정도 비쌉니다 (프로세서마다 다름). 이것은 비용을 증가시킬 수 있습니다.
이를 통해 여러 모델의 장단점을 고려할 수 있습니다.
Objective-C는 ARC (자동 참조 계산)에 중점을 둡니다. 그들의 접근 방식은 모든 것에 참조 카운팅을 사용하는 것입니다. 사이클 감지 기능이 없기 때문에 (알고있는) 프로그래머는 사이클이 발생하는 것을 막을 것으로 예상되므로 개발 시간이 걸립니다. 그들의 이론은 포인터에 자주 할당되는 것은 아니며, 컴파일러는 참조 카운트 증가 / 감소로 인해 객체가 죽을 수없는 상황을 식별하고 그 증가 / 감소를 완전히 제거 할 수 있다는 것입니다. 따라서 참조 계산 비용을 최소화합니다.
CPython은 하이브리드 메커니즘을 사용합니다. 참조 횟수를 사용하지만주기를 식별하고 해제하는 가비지 수집기도 있습니다. 이는 두 가지 접근 방식을 모두 희생하면서 두 세계의 이점을 제공합니다. CPython은 참조 횟수 와주기를 감지하기 위해 책을 보관하십시오. CPython은 두 가지 방법으로이 문제를 해결합니다. CPython은 실제로 완전히 멀티 스레드되지는 않습니다. 멀티 스레딩을 제한하는 GIL이라는 잠금 장치가 있습니다. 이는 CPython이 원자 단위가 아닌 일반 증분 / 감소를 사용할 수 있다는 것을 의미합니다. CPython도 해석됩니다. 즉, 변수에 할당하는 것과 같은 연산은 이미 1이 아닌 소수의 명령어를 사용합니다. C 코드에서 빠르게 수행되는 증분 / 감소를 수행하는 추가 비용은 문제가되지 않습니다. 이미이 비용을 지불했습니다.
Java는 참조 카운트 시스템을 전혀 보장하지 않는 접근 방식을 취합니다. 실제로 스펙은 말을하지 않는 어떤 객체가 자동 스토리지 관리 시스템이 될 것보다는 다른 관리 방법에 대한합니다. 그러나이 사양은 사이클을 처리하는 방식으로 가비지 수집 될 것이라는 가정을 강력히 암시합니다. 객체가 만료되는시기를 지정하지 않으면 java는 시간 증가 / 감소를 낭비하지 않는 수집기를 자유롭게 사용할 수 있습니다. 실제로 세대 별 가비지 수집기와 같은 영리한 알고리즘은 회수되는 데이터를 보지 않고도 많은 간단한 경우를 처리 할 수 있습니다 (아직 참조되는 데이터 만보아야 함).
그래서 우리는이 세 가지 각각이 절충을해야한다는 것을 알 수 있습니다. 어떤 트레이드 오프가 가장 좋은지는 언어가 사용되는 방식의 본질에 크게 좌우됩니다.
finalize
Java의 GC에 대해 피기 백을 받았지만 핵심 가비지 콜렉션은 죽은 오브젝트가 아니라 살아있는 오브젝트에 관심이 있습니다. 일부 GC 시스템에서 (일부 Java 구현을 포함 할 수 있음), 어떤 용도로 사용되지 않는 스토리지와 객체를 나타내는 비트를 구별하는 유일한 것은 전자에 대한 참조가있을 수 있습니다. 종료자가있는 개체는 특수 목록에 추가되지만 다른 개체에는 유니버스의 저장소가 사용자 코드에 포함 된 참조를 제외하고 개체와 연결되어 있다고 말하는 것이 없습니다. 마지막으로 이러한 참조를 덮어 쓰면 메모리의 비트 패턴 은 유니버스의 어떤 것도 인식하는지 여부에 관계없이 즉시 개체로 인식 할 수 없게됩니다.
가비지 수집의 목적은 참조가없는 객체를 파괴하는 것이 아니라 세 가지를 달성하는 것입니다.
도달 가능한 참조가없는 개체를 식별하는 약한 참조를 무효화합니다.
파이널 라이저를 사용하여 시스템의 오브젝트 목록을 검색하여 도달 가능한 참조가없는 오브젝트가 있는지 확인하십시오.
객체가 사용하지 않는 스토리지 영역을 식별하고 통합합니다.
GC의 주요 목표는 # 3이며,이를 수행하기 전에 대기 시간이 길어질수록 통합 기회가 더 많아 질 수 있습니다. 스토리지를 즉시 사용하는 경우에는 # 3을 수행하는 것이 합리적이지만 그렇지 않으면 연기하는 것이 더 합리적입니다.
귀하의 질문에 대해 다시 말하고 일반화를 제안하겠습니다.
Java가 GC 프로세스를 강력하게 보장하지 않는 이유는 무엇입니까?
이를 염두에두고 여기에서 답변을 빠르게 스크롤하십시오. 지금까지 7 개 (이것은 포함하지 않음)가 있으며 주석 스레드는 상당히 많습니다.
그게 당신의 대답입니다.
GC는 어렵다. 많은 고려 사항, 많은 다른 트레이드 오프 및 궁극적으로 매우 다른 접근 방식이 있습니다. 이러한 접근 방식 중 일부는 GC가 필요하지 않은 즉시 객체를 실현할 수있게합니다. 다른 사람들은 그렇지 않습니다. 계약을 느슨하게 유지함으로써 Java는 구현 자에게 더 많은 옵션을 제공합니다.
물론이 결정에도 트레이드 오프가 있습니다. 계약을 느슨하게 유지함으로써 Java는 대부분 프로그래머가 소멸자에 의존 할 수있는 능력을 제거합니다. 이것은 C ++ 프로그래머들이 종종 ([citation needed];)) 그리워하는 것이므로 그다지 중요하지 않은 트레이드 오프는 아닙니다. 필자는 특정 메타 결정에 대한 논의를 보지 못했지만 Java 사람들은 아마도 더 많은 GC 옵션을 사용함으로써 얻을 수있는 이점이 객체가 언제 파괴 될 것인지 프로그래머에게 알릴 수있는 이점보다 중요하다고 결정했을 것입니다.
* finalize
방법이 있지만이 답변의 범위를 벗어난 여러 가지 이유 때문에이 방법에 의존하는 것은 어렵고 좋은 생각이 아닙니다.
개발자가 작성한 명시 적 코드없이 메모리를 처리하는 두 가지 전략이 있습니다. 가비지 수집 및 참조 횟수.
가비지 콜렉션은 개발자가 어리석은 짓을하지 않는 한 "작동"한다는 장점이 있습니다. 참조 횟수를 사용하면 참조주기를 가질 수 있습니다. 즉, "작동"하지만 개발자가 영리해야합니다. 따라서 가비지 수집에 도움이됩니다.
참조 횟수를 사용하면 참조 횟수가 0으로 떨어지면 개체가 즉시 사라집니다. 이는 참조 카운팅의 이점입니다.
가비지 수집의 팬을 믿으면 가비지 수집이 더 빠르며, 참조 수의 팬을 믿으면 가비지 수집이 더 빠릅니다.
동일한 목표를 달성하는 것은 두 가지 다른 방법입니다 .Java는 하나의 방법을 선택했으며 Objective-C는 다른 방법을 선택했습니다.
가비지 수집에서 참조 계산으로 Java를 변경하는 것은 많은 코드 변경이 필요하기 때문에 중요한 작업입니다.
이론적으로 Java는 가비지 수집과 참조 계산을 혼합하여 구현할 수 있습니다. 참조 수가 0이면 객체에 도달 할 수 없지만 반드시 다른 방법은 아닙니다. 그래서 당신은 할 수 참조 카운트를 유지하고 삭제할 객체를 자신의 참조 횟수가 0 인 경우 (다음에 연결할 참조 사이클 내에서 객체를 잡을 수시로 가비지 컬렉션을 실행). 가비지 수집에 참조 계산을 추가하는 것이 좋지 않다고 생각하는 사람들과 가비지 수집을 참조 계산에 추가하는 것이 나쁜 생각이라고 생각하는 사람들은 세계가 50/50으로 나뉘어져 있다고 생각합니다. 그래서 이것은 일어나지 않을 것입니다.
따라서 Java 는 참조 수가 0이되면 즉시 오브젝트를 삭제하고 나중에 도달 할 수없는주기 내에 오브젝트를 삭제할 수 있습니다. 그러나 이것은 디자인 결정이며 Java는 그 결정에 반대했습니다.
더 이상 객체에 대한 참조가 없을 때 이해의 어려움에 대한 다른 모든 성능 주장과 토론은 정확하지만 언급 할 가치가 있다고 생각하는 다른 아이디어는 이와 같은 것을 고려하는 적어도 하나의 JVM (azul)이 있다는 것입니다 그것은 본질적으로 vm 스레드가 지속적으로 참조를 검사하여 참조를 삭제하려고 시도하는 병렬 gc를 구현한다는 것입니다. 기본적으로 힙을 계속 둘러보고 참조되지 않은 메모리를 회수하려고 시도합니다. 이로 인해 약간의 성능 비용이 발생하지만 기본적으로 GC 시간이 0이거나 매우 짧습니다. (이는 끊임없이 확장되는 힙 크기가 시스템 RAM을 초과하지 않으면 Azul이 혼란스러워하고 용이없는 경우가 아닙니다)
TLDR JVM에는 이런 종류의 것이 존재합니다. 그것은 단지 특별한 jvm이며 다른 엔지니어링 손상과 같은 단점이 있습니다.
면책 조항 : 나는 Azul과 아무런 관련이 없으며 우리는 이전 직장에서 그것을 사용했습니다.
이 모든 일이 진행되는 이유는 궁극적으로 속도 때문입니다. 프로세서가 무한히 빠르거나 실용적 이도록 (예 : 초당 1,000,000,000,000,000,000,000,000,000,000,000) 작업을 수행하면 역 참조 된 객체가 삭제되도록하는 등 각 운영자간에 미친 길고 복잡한 일이 발생할 수 있습니다. 초당 해당 작업 수가 사실이 아니며 대부분의 다른 답변에서 설명하는 것처럼 실제로 복잡하고 리소스를 많이 사용하므로 가비지 수집이 존재하므로 프로그램이 실제로 달성하려는 작업에 집중할 수 있습니다. 빠른 방법.