JavaRush /Java Blog /Random-KO /Java의 PhantomReference

Java의 PhantomReference

Random-KO 그룹에 게시되었습니다
안녕하세요! 오늘 수업에서는 Java의 Phantom References에 대해 자세히 설명하겠습니다. 이러한 링크는 어떤 종류이며, 왜 "팬텀"이라고 불리며, 어떻게 사용합니까? 아시다시피 Java에는 4가지 유형의 링크가 있습니다.
  1. StrongReference (객체를 생성할 때 생성하는 일반 참조):

    Cat cat = new Cat()

    이 예에서 cat은 강력한 참조입니다.

  2. SoftReference (소프트 참조). 우리는 이 링크들에 대해 강의를 했습니다 .

  3. WeakReference (약한 참조). 여기 에서도 그들에 대한 강의가 있었습니다 .

  4. PhantomReference (팬텀 참조).

마지막 세 가지 유형의 개체가 형식화됩니다(예: SoftReference<Integer> , WeakReference<MyClass> ). Classes SoftReference및 class 에서 상속 WeakReference됩니다 . 이러한 클래스로 작업할 때 가장 중요한 방법은 다음과 같습니다. PhantomReferenceReference
  • 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팬텀 링크에 대해서는 반환했습니다. 셋째 , 팬텀 참조의 주요 사용 영역은 메모리에서 객체를 제거하는 복잡한 절차입니다. 그게 다야! :) 이것으로 오늘 수업을 마치겠습니다. 하지만 이론만으로는 멀리 갈 수 없으므로 이제 문제 해결로 돌아가야 할 때입니다! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION