얼마 전 저는 수석 Java 개발 엔지니어로 일자리를 찾고 있는 몇몇 후보자들을 인터뷰했습니다. 저는 종종 인터뷰를 하면서 "Java의 약한 참고자료를 소개해줄 수 있나요?"라고 말하는데, 면접관이 "글쎄, 가비지 컬렉션과 관련이 있나요?"라고 말하면 기본적으로는 만족할 것이고, 안 할 것입니다. 답이 사물의 근본 원인을 파헤치는 논문에 대한 설명일 것이라고 기대하지 마세요.
그런데 기대와 달리 평균 5년의 개발 경험과 고학력의 20명 남짓한 지원자 중에서 약한 레퍼런스의 존재를 알고 있는 사람은 단 두 명뿐이었는데 실제로는 이 두 사람 중 한 명만이 실제로는 한 명뿐이었다는 사실을 알고 놀랐습니다. 이에 대해 알아보세요. 인터뷰 과정에서 누가 갑자기 '그럼 그렇지'라고 말할까 싶어 이것저것 유도도 해보았으나 결과는 매우 실망스러웠다. 결국 약한 참조는 매우 유용한 기능인데 이 기능은 7년 전 Java 1.2가 출시되었을 때 도입되었습니다.
글쎄, 이 글을 읽고 당신이 약한 참조에 대한 전문가가 될 것이라고 기대하지는 않지만, 적어도 약한 참조가 무엇인지, 어떻게 사용하는지, 어떤 시나리오에서 사용되는지는 이해해야 한다고 생각합니다. 잘 알려지지 않은 개념이기 때문에 앞의 세 가지 질문에 대해 간략하게 설명하겠습니다.
강력한 참조
강한 참조란 우리가 자주 사용하는 참조어로, 다음과 같이 작성됩니다.
다음과 같이 코드 코드를 복사합니다.
StringBuffer 버퍼 = 새로운 StringBuffer();
위의 코드는 StringBuffer 객체를 생성하고 이 객체에 대한 (강력한) 참조를 변수 버퍼에 저장합니다. 네, 이것은 소아과 수술입니다. (이런 말을 해서 죄송합니다.) 강력한 참조의 가장 중요한 점은 참조를 강력하게 만들어 가비지 수집기와의 상호 작용을 결정한다는 것입니다. 특히, 강력한 참조 링크 문자열(강력한 연결 가능)을 통해 개체에 연결할 수 있는 경우 해당 개체는 재활용되지 않습니다. 작업 중인 개체를 재활용하지 않으려는 경우 이것이 바로 필요한 것입니다.
하지만 강한 인용문은 너무 강해요
프로그램에서 클래스를 확장 불가능하게 설정하는 것은 다소 드문 일입니다. 물론 클래스를 최종으로 표시하면 가능합니다. 또는 알 수 없는 수의 특정 구현이 포함된 팩토리 메서드를 통해 인터페이스(인터페이스)를 반환하는 것보다 더 복잡할 수도 있습니다. 예를 들어 Widget이라는 클래스를 사용하고 싶지만 이 클래스는 상속할 수 없기 때문에 새로운 기능을 추가할 수 없습니다.
하지만 Widget 개체에 대한 추가 정보를 추적하려면 어떻게 해야 할까요? 각 객체의 일련 번호를 기록해야 하지만 Widget 클래스에는 이 속성이 포함되어 있지 않고 확장할 수 없기 때문에 이 속성을 추가할 수 없다고 가정합니다. 사실 전혀 문제가 없습니다. HashMap은 위의 문제를 완벽하게 해결할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
serialNumberMap.put(widget, widgetSerialNumber);
표면적으로는 괜찮아 보일 수 있지만 위젯 개체에 대한 강한 참조는 문제를 일으킬 수 있습니다. 위젯 일련번호가 더 이상 필요하지 않으면 지도에서 해당 항목을 제거해야 한다는 것을 확신할 수 있습니다. 제거하지 않으면 메모리 누수가 발생할 수 있으며, 수동으로 제거할 때 사용 중인 위젯이 삭제되어 유효한 데이터가 손실될 수 있습니다. 실제로 이러한 문제는 매우 유사합니다. 이는 가비지 수집 메커니즘이 없는 언어가 메모리를 관리할 때 자주 직면하는 문제입니다. 하지만 우리는 가비지 수집 메커니즘을 갖춘 Java 언어를 사용하고 있기 때문에 이 문제에 대해 걱정할 필요가 없습니다.
강력한 참조로 인해 발생할 수 있는 또 다른 문제는 특히 이미지와 같은 대용량 파일의 경우 캐싱입니다. 사용자가 제공한 이미지를 처리해야 하는 프로그램이 있다고 가정해 보겠습니다. 일반적인 접근 방식은 이미지 데이터를 캐시하는 것입니다. 디스크에서 이미지를 로드하는 데 비용이 많이 들고 동시에 동일한 이미지 데이터의 복사본이 두 개 있는 것을 피하고 싶기 때문입니다. 동시에 기억에 남는다.
캐싱의 목적은 불필요한 파일을 다시 로드하는 것을 방지하는 것입니다. 캐시에는 항상 메모리의 이미지 데이터에 대한 참조가 포함되어 있다는 것을 금방 알 수 있습니다. 강력한 참조를 사용하면 이미지 데이터가 메모리에 유지됩니다. 따라서 이미지 데이터가 더 이상 필요하지 않은 시기를 결정하고 가비지 수집기가 회수할 수 있도록 캐시에서 수동으로 제거해야 합니다. 따라서 다시 한 번 가비지 수집기가 수행하는 작업을 수행하고 청소할 개체를 수동으로 결정해야 합니다.
약한 참조
약한 참조는 단순히 객체를 메모리에 유지하는 데 그다지 강력하지 않은 참조입니다. WeakReference를 사용하면 가비지 수집기가 참조된 개체가 재활용되는 시기를 결정하고 메모리에서 개체를 제거하는 데 도움이 됩니다. 다음과 같이 약한 참조를 만듭니다.
다음과 같이 코드 코드를 복사합니다.
eakReference<Widget> WeakWidget = new WeakReference<Widget>(위젯);
WeakWidget.get()을 사용하여 실제 Widget 객체를 얻을 수 있습니다. 약한 참조는 가비지 수집기가 위젯을 재활용하는 것을 막을 수 없기 때문에 (위젯 객체에 대한 강력한 참조가 없는 경우) 사용할 때 null이 갑자기 반환된다는 것을 알게 될 것입니다. 얻다.
위의 위젯 시퀀스 번호 기록 문제를 해결하는 가장 쉬운 방법은 Java에 내장된 WeakHashMap 클래스를 사용하는 것입니다. WeakHashMap은 HashMap과 거의 동일합니다. 유일한 차이점은 해당 키(값 아님!!!)가 WeakReference에 의해 참조된다는 것입니다. WeakHashMap 키가 가비지로 표시되면 이 키에 해당하는 항목이 자동으로 제거됩니다. 이는 불필요한 위젯 객체를 수동으로 삭제하는 위의 문제를 방지합니다. WeakHashMap은 HashMap이나 Map으로 쉽게 변환될 수 있습니다.
참조 대기열
약한 참조 개체가 null을 반환하기 시작하면 약한 참조가 가리키는 개체는 가비지로 표시됩니다. 그리고 이 약한 참조 개체(그것이 가리키는 개체가 아님)는 쓸모가 없습니다. 일반적으로 이때 일부 정리 작업을 수행해야 합니다. 예를 들어, WeakHashMap은 무기한으로 커지는 의미 없는 약한 참조를 저장하지 않기 위해 이때 쓸모 없는 항목을 제거합니다.
참조 대기열을 사용하면 원치 않는 참조를 쉽게 추적할 수 있습니다. WeakReference를 생성할 때 ReferenceQueue 객체를 전달하면 참조가 가리키는 객체가 가비지로 표시되면 참조 객체가 자동으로 참조 큐에 추가됩니다. 그런 다음 이러한 쓸모 없는 참조 개체를 처리하기 위해 일부 정리 작업을 수행하는 등 고정된 기간에 들어오는 참조 큐를 처리할 수 있습니다.
4가지 종류의 참고자료
실제로 Java에는 강한 참조, 약한 참조, 약한 참조 및 가상 참조의 네 가지 유형의 참조가 있습니다. 위 섹션에서는 강한 참조와 약한 참조를 소개하고, 다음에서는 나머지 두 가지인 소프트 참조와 가상 참조에 대해 설명합니다.
소프트 참조
소프트 참조는 가비지 수집 기간이 약한 참조보다 가리키는 개체를 재활용하는 것을 방지하는 기능이 더 강력하다는 점을 제외하면 기본적으로 약한 참조와 동일합니다. 약한 참조로 개체에 접근할 수 있는 경우 해당 개체는 다음 수집 주기에서 가비지 수집기에 의해 삭제됩니다. 그러나 소프트 참조에 도달할 수 있으면 개체는 더 오랫동안 메모리에 유지됩니다. 가비지 수집기는 메모리가 부족한 경우 이러한 소프트 참조로 연결할 수 있는 개체만 회수합니다.
소프트 참조로 접근할 수 있는 객체는 약한 참조로 접근할 수 있는 객체보다 메모리에 더 오래 머물기 때문에 이 기능을 캐싱에 사용할 수 있습니다. 이런 방식으로 많은 것을 절약할 수 있습니다. 가비지 수집기는 현재 도달 가능한 유형과 처리를 위한 메모리 소비 정도에 관심을 갖습니다.
팬텀 레퍼런스
소프트 참조나 약한 참조와 달리 가상 참조가 가리키는 객체는 매우 취약하며, get 메소드를 통해 그들이 가리키는 객체를 얻을 수 없습니다. 유일한 기능은 가리키는 개체가 재활용된 후 참조 큐에 추가되어 참조가 가리키는 개체가 삭제되었음을 기록하는 것입니다.
약한 참조가 가리키는 객체에 약한 참조가 도달할 수 있게 되면 약한 참조가 참조 큐에 추가됩니다. 이 작업은 개체 삭제 또는 가비지 수집이 실제로 발생하기 전에 발생합니다. 이론적으로 재활용하려는 개체는 비호환 소멸자 메서드에서 부활될 수 있습니다. 그러나 이 약한 참조는 파괴될 것입니다. 가상 참조는 그것이 가리키는 객체가 메모리에서 제거된 후에만 참조 큐에 추가됩니다. 해당 get 메소드는 가리키는 거의 파괴된 객체가 부활되는 것을 방지하기 위해 계속 null을 반환합니다.
가상 참조에는 두 가지 주요 사용 시나리오가 있습니다. 이를 통해 참조하는 개체가 메모리에서 제거되는 시기를 정확히 알 수 있습니다. 실제로 이것이 Java의 유일한 방법입니다. 이는 이미지와 같은 대용량 파일을 처리할 때 특히 그렇습니다. 이미지 데이터 개체를 재활용해야 한다고 결정한 경우 가상 참조를 사용하여 다음 이미지를 계속 로드하기 전에 개체가 재활용되는지 확인할 수 있습니다. 이렇게 하면 끔찍한 메모리 오버플로 오류를 최대한 피할 수 있습니다.
두 번째 요점은 가상 참조가 소멸 중에 많은 문제를 피할 수 있다는 것입니다. finalize 메소드는 해당 객체를 가리키는 강력한 참조를 생성하여 곧 소멸될 객체를 부활시킬 수 있습니다. 그러나 finalize 메서드를 재정의하는 개체를 재활용하려면 별도의 두 가지 가비지 수집 주기를 거쳐야 합니다. 첫 번째 주기에서는 객체가 재활용 가능한 것으로 표시되어 폐기될 수 있습니다. 하지만 이 물체가 파괴 과정에서 부활할 가능성은 여전히 약간 있기 때문입니다. 이 경우 개체가 실제로 삭제되기 전에 가비지 수집기를 다시 실행해야 합니다. 소멸은 시기적절하지 않을 수 있으므로 개체 소멸이 호출되기 전에 불확실한 수의 가비지 수집 주기를 거쳐야 합니다. 이는 실제로 개체를 정리하는 데 큰 지연이 있을 수 있음을 의미합니다. 이것이 대부분의 힙이 가비지로 표시될 때 여전히 성가신 메모리 부족 오류가 발생하는 이유입니다.
가상 참조를 사용하면 위의 상황이 해결됩니다. 가상 참조가 참조 큐에 추가되면 파괴된 객체를 얻을 수 있는 방법이 전혀 없습니다. 왜냐하면 이때 개체는 메모리에서 소멸되었기 때문입니다. 가상 참조는 가리키는 개체를 재생성하는 데 사용할 수 없기 때문에 해당 개체는 가비지 수집의 첫 번째 주기에서 정리됩니다.
분명히 finalize 메서드를 재정의하는 것은 권장되지 않습니다. 가상 참조는 확실히 안전하고 효율적이므로 finalize 메서드를 제거하면 가상 머신이 훨씬 더 단순해질 수 있습니다. 물론 더 많은 것을 달성하기 위해 이 메서드를 재정의할 수도 있습니다. 그것은 모두 개인적인 선택에 달려 있습니다.
요약
이것을 보고 많은 사람들이 불평하기 시작하는 것 같습니다. 왜 지난 10년 동안의 오래된 API에 대해 이야기하고 있습니까? 글쎄요, 제 경험으로 볼 때 많은 Java 프로그래머들은 이 지식을 잘 모르는 것 같습니다. -깊은 이해가 필요하며, 이 글을 통해 모두가 뭔가를 얻을 수 있기를 바랍니다.