안녕하세요! 오늘 수업에서는 Java의 Phantom References에 대해 자세히 설명하겠습니다. 이러한 링크는 어떤 종류이며, 왜 "팬텀"이라고 불리며, 어떻게 사용합니까? 아시다시피 Java에는 4가지 유형의 링크가 있습니다.
-
StrongReference (객체를 생성할 때 생성하는 일반 참조):
Cat cat = new Cat()
이 예에서 cat은 강력한 참조입니다.
-
SoftReference (소프트 참조). 우리는 이 링크들에 대해 강의를 했습니다 .
-
WeakReference (약한 참조). 여기 에서도 그들에 대한 강의가 있었습니다 .
-
PhantomReference (팬텀 참조).
SoftReference
및 class 에서 상속 WeakReference
됩니다 . 이러한 클래스로 작업할 때 가장 중요한 방법은 다음과 같습니다. PhantomReference
Reference
-
get()
- 이 링크가 참조하는 객체를 반환합니다. clear()
— 객체에 대한 참조를 삭제합니다.
SoftReference
및 에 대한 강의에서 이러한 방법을 기억합니다 WeakReference
. 다양한 유형의 링크에 따라 다르게 작동한다는 점을 기억하는 것이 중요합니다. 오늘은 처음 세 가지 유형을 자세히 고려하지 않고 팬텀 링크에 대해 이야기하겠습니다. 다른 유형의 링크도 다룰 예정이지만 팬텀 링크와 팬텀 링크의 차이점에 대해 설명하는 부분에서만 가능합니다. 가다! :) 우선 왜 팬텀 링크가 필요한지부터 시작해 보겠습니다. 아시 다시피 가비지 컬렉터 (Garbage Collector 또는 gc) 는 불필요한 Java 객체에서 메모리를 해제하는 역할을 합니다 . 수집기는 두 번의 "패스"를 통해 개체를 제거합니다. 첫 번째 패스에서는 개체만 살펴보고 필요한 경우 해당 개체를 "불필요하며 삭제해야 함"으로 표시합니다. 이 객체에 재정의된 메소드가 있는 경우 finalize()
해당 객체가 호출됩니다. 또는 운에 따라 호출되지 않습니다. 아마도 이것이 finalize()
변덕스러운 일이라는 것을 기억하실 것입니다. :) 수집기의 두 번째 단계에서 개체가 삭제되고 메모리가 해제됩니다. 가비지 수집기의 이러한 예측할 수 없는 동작은 우리에게 많은 문제를 야기합니다. 가비지 수집기가 언제 작동을 시작할지 정확히 알 수 없습니다. 메소드가 호출될지 여부는 알 수 없습니다 finalize()
. 또한 작업 중에 finalize()
개체에 대한 강력한 링크가 생성될 수 있으며 그런 다음 전혀 삭제되지 않습니다. 메모리가 부족한 시스템에서는 이로 인해 OutOfMemoryError
. 이 모든 것이 우리를 팬텀 링크를 사용하도록 유도합니다 . 요점은 이것이 가비지 수집기의 동작을 변경한다는 것입니다. 객체에 가상 링크만 남아 있는 경우 객체는 다음을 갖습니다.
-
메소드가 호출됩니다
finalize()
(재정의된 경우). -
작업 후
finalize()
아무것도 변경되지 않았고 객체를 여전히 삭제할 수 있는 경우 객체에 대한 팬텀 참조가 특수 대기열에 배치됩니다ReferenceQueue
.
clear()
. 예를 살펴보겠습니다. 먼저 일부 데이터를 저장할 테스트 클래스를 만들어 보겠습니다.
public class TestClass {
private StringBuffer data;
public TestClass() {
this.data = new StringBuffer();
for (long i = 0; i < 50000000; i++) {
this.data.append('x');
}
}
@Override
protected void finalize() {
System.out.println("У an object TestClass вызван метод finalize!!!");
}
}
우리는 더 많은 메모리를 차지하기 위해 객체를 생성할 때 의도적으로 데이터와 함께 객체를 "로드"합니다(각 객체에 5천만 개의 "x" 문자 추가). 또한 메서드 finalize()
가 제대로 작동하는지 확인하기 위해 구체적으로 메서드를 재정의합니다. 다음으로 에서 상속할 클래스가 필요합니다 PhantomReference
. 왜 그런 수업이 필요한가요? 간단 해. clear()
이 방법으로 우리는 팬텀 참조가 실제로 지워졌는지(따라서 객체가 삭제되었는지) 확인하기 위해 메서드에 추가 논리를 추가할 수 있습니다 .
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {
public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {
super(obj, queue);
Thread thread = new QueueReadingThread<TestClass>(queue);
thread.start();
}
public void cleanup() {
System.out.println("Очистка фантомной ссылки! Удаление an object из памяти!");
clear();
}
}
다음으로, 가비지 수집기가 해당 작업을 수행할 때까지 기다리는 별도의 스레드가 필요하며 ReferenceQueue
팬텀 링크가 대기열에 나타납니다. 해당 링크가 대기열에 들어가자마자 해당 메서드가 호출됩니다 cleanup()
.
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class QueueReadingThread<TestClass> extends Thread {
private ReferenceQueue<TestClass> referenceQueue;
public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
this.referenceQueue = referenceQueue;
}
@Override
public void run() {
System.out.println("Поток, отслеживающий очередь, стартовал!");
Reference ref = null;
//ждем, пока в очереди появятся ссылки
while ((ref = referenceQueue.poll()) == null) {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
throw new RuntimeException("Поток " + getName() + " был прерван!");
}
}
//How только в очереди появилась фантомная link - очистить ее
((MyPhantomReference) ref).cleanup();
}
}
그리고 마지막으로 메소드가 필요합니다 main()
. 이를 별도의 클래스에 넣겠습니다 Main
. 그 안에서 우리는 객체 TestClass
, 그것에 대한 팬텀 참조, 팬텀 참조를 위한 큐를 생성할 것입니다. 그런 다음 가비지 수집기를 호출하여 무슨 일이 일어나는지 살펴보겠습니다. :)
import java.lang.ref.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
Reference ref = new MyPhantomReference<>(new TestClass(), queue);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Вызывается сборка мусора!");
System.gc();
Thread.sleep(300);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Вызывается сборка мусора!");
System.gc();
}
}
콘솔 출력: ref = MyPhantomReference@4554617c 대기열을 모니터링하는 스레드가 시작되었습니다! 쓰레기 수거가 호출됩니다! TestClass 객체에서 finalize 메소드가 호출되었습니다!!! ref = MyPhantomReference@4554617c 가비지 수집이 호출되고 있습니다! 팬텀링크 정리! 메모리에서 객체를 제거합니다! 여기서 무엇을 볼 수 있나요? 모든 일이 우리가 계획한 대로 일어났습니다! 우리 객체 클래스에는 재정의된 메서드가 있었고 finalize()
수집기가 실행되는 동안 호출되었습니다. 다음으로 팬텀 링크가 대기열에 추가되었습니다 ReferenceQueue
. 거기에는 콘솔에 출력을 추가하기 위해 clear()
수행한 메서드가 있었습니다 . cleanup()
결과적으로 개체가 메모리에서 제거되었습니다. 이제 그것이 어떻게 작동하는지 정확히 알 수 있습니다 :) 물론, 팬텀 링크와 관련된 모든 이론을 외울 필요는 없습니다. 하지만 최소한 주요 사항을 기억하면 좋을 것입니다. 첫째 , 이것들은 가장 약한 고리입니다. 객체에 대한 다른 참조가 없는 경우에만 작동합니다. 위에서 제공한 링크 목록은 강도의 내림차순입니다. StrongReference
-> SoftReference
-> WeakReference
-> PhantomReference
팬텀 링크는 개체에 대한 Strong, Soft 또는 Weak 링크가 없는 경우에만 전투에 참여합니다. :) 둘째 , 방법 get()
팬텀 링크의 경우 항상 를 반환합니다 null
. 다음은 세 가지 유형의 자동차에 대해 세 가지 유형의 링크를 만드는 간단한 예입니다.
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
HybridAuto hybrid = new HybridAuto();
F1Car f1car = new F1Car();
SoftReference<Sedan> softReference = new SoftReference<>(sedan);
System.out.println(softReference.get());
WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
System.out.println(weakReference.get());
ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();
PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
System.out.println(phantomReference.get());
}
}
콘솔 출력: Sedan@4554617c HybridAuto@74a14482 null 이 메소드는 get()
소프트 링크와 약한 링크에 대해서는 매우 일반적인 개체를 반환했지만 null
팬텀 링크에 대해서는 반환했습니다. 셋째 , 팬텀 참조의 주요 사용 영역은 메모리에서 객체를 제거하는 복잡한 절차입니다. 그게 다야! :) 이것으로 오늘 수업을 마치겠습니다. 하지만 이론만으로는 멀리 갈 수 없으므로 이제 문제 해결로 돌아가야 할 때입니다! :)
GO TO FULL VERSION