JavaRush /จาวาบล็อก /Random-TH /PhantomReference ใน Java

PhantomReference ใน Java

เผยแพร่ในกลุ่ม
สวัสดี! ในบทเรียนวันนี้ เราจะพูดถึงรายละเอียดเกี่ยวกับ PhantomReferences ใน Java ลิงก์เหล่านี้คืออะไร ทำไมจึงเรียกว่า "phantom" และใช้งานอย่างไร อย่างที่คุณจำได้ มีลิงก์ 4 ประเภทใน Java:
  1. StrongReference (การอ้างอิงปกติที่เราสร้างเมื่อสร้างวัตถุ):

    Cat cat = new Cat()

    catในตัวอย่างนี้เป็นการอ้างอิงที่รัดกุม

  2. SoftReference (การอ้างอิงแบบอ่อน) เรามีการบรรยายเกี่ยวกับลิงค์เหล่านี้

  3. WeakReference (การอ้างอิงที่อ่อนแอ) มีการบรรยายเกี่ยวกับพวกเขาที่นี่ ด้วย

  4. PhantomReference (การอ้างอิง Phantom)

มีการพิมพ์ออบเจ็กต์ของสามประเภทสุดท้าย (เช่นSoftReference<Integer> , WeakReference<MyClass> ) คลาสSoftReferenceและWeakReferenceสืบทอดPhantomReferenceจากReferenceคลาส วิธีการที่สำคัญที่สุดเมื่อทำงานกับคลาสเหล่านี้คือ:
  • get()- ส่งคืนอ็อบเจ็กต์ที่ลิงก์นี้อ้างถึง;

  • clear()— ลบการอ้างอิงไปยังวัตถุ

คุณจำวิธีการเหล่านี้ได้จากการบรรยายเกี่ยวกับSoftReferenceและWeakReference. สิ่งสำคัญคือต้องจำไว้ว่าลิงก์เหล่านี้ทำงานแตกต่างกันกับลิงก์ประเภทต่างๆ วันนี้เราจะไม่พิจารณารายละเอียดสามประเภทแรก แต่จะพูดถึงลิงก์ Phantom เราจะพูดถึงลิงก์ประเภทอื่น ๆ ด้วย แต่เฉพาะในส่วนที่เราจะพูดถึงว่าลิงก์ Phantom แตกต่างจากลิงก์เหล่านั้นอย่างไร ไป! :) มาเริ่มกันที่ว่าทำไมเราถึงต้องการลิงค์ Phantom ตั้งแต่แรก ดังที่คุณทราบ ตัวรวบรวมขยะ (Garbage Collector หรือ gc) มีหน้าที่รับผิดชอบในการเพิ่มหน่วยความจำจากอ็อบเจ็กต์ Java ที่ไม่จำเป็น นักสะสมจะลบวัตถุออกเป็นสอง "ผ่าน" ในการผ่านครั้งแรก ระบบจะดูเฉพาะวัตถุ และหากจำเป็น จะทำเครื่องหมายว่า “ไม่จำเป็นและจะถูกลบ” หากวัตถุนี้มีเมธอดที่ถูกแทนที่finalize()ก็จะถูกเรียก หรือไม่เรียกว่า - ขึ้นอยู่กับโชคของคุณ คุณคงจำได้ว่านี่finalize()เป็นสิ่งที่ไม่แน่นอน :) ในการผ่านครั้งที่สองของตัวสะสม วัตถุจะถูกลบและหน่วยความจำจะถูกปลดปล่อย พฤติกรรมที่คาดเดาไม่ได้ของคนเก็บขยะนี้สร้างปัญหามากมายให้เรา เราไม่รู้แน่ชัดว่าคนเก็บขยะจะเริ่มทำงานเมื่อใด เราไม่รู้ว่าจะเรียก method นี้หรือfinalize()เปล่า นอกจากนี้ ในระหว่างการดำเนินการfinalize()คุณสามารถสร้างลิงก์ที่รัดกุมไปยังออบเจ็กต์ได้ จากนั้นจะไม่ถูกลบเลย บนระบบที่ต้องใช้หน่วยความจำมาก สิ่งนี้สามารถนำไปสู่OutOfMemoryError​​. ทั้งหมดนี้ผลักดันให้เราใช้ลิงก์Phantom ประเด็นก็คือสิ่งนี้จะเปลี่ยนพฤติกรรมของคนเก็บขยะ หากมีลิงก์หลอกเหลืออยู่ที่ออบเจ็กต์ มันก็จะมี:
  • เรียกเมธอดนี้finalize()(หากถูกแทนที่)

  • หากหลังเลิกงานfinalize()ไม่มีอะไรเปลี่ยนแปลงและยังสามารถลบอ็อบเจ็กต์ได้ การอ้างอิง phantom ไปยังอ็อบเจ็กต์จะถูกวางไว้ในคิวพิเศษReferenceQueue-

สิ่งที่สำคัญที่สุดที่ต้องทำความเข้าใจเมื่อทำงานกับการอ้างอิง phantom คือวัตถุจะไม่ถูกลบออกจากหน่วยความจำตราบใดที่การอ้างอิง phantom อยู่ในคิวนี้ มันจะถูกลบหลังจากลิงค์ phantom 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!!!");
   }
}
เราจงใจ "โหลด" วัตถุด้วยข้อมูลเมื่อสร้างวัตถุเหล่านั้น (เพิ่มอักขระ "x" 50 ล้านตัวให้กับแต่ละวัตถุ) เพื่อใช้หน่วยความจำมากขึ้น นอกจากนี้ เรายังแทนที่วิธีการนี้โดยเฉพาะfinalize()เพื่อดูว่าได้ผลหรือไม่ ต่อไปเราต้องการคลาสที่จะสืบทอดจากPhantomReference. ทำไมเราถึงต้องการชั้นเรียนเช่นนี้? มันง่ายมาก วิธีนี้ทำให้เราสามารถเพิ่มตรรกะเพิ่มเติมให้กับวิธีการclear()เพื่อดูว่าการอ้างอิง Phantom ได้รับการล้างแล้วจริง ๆ (และดังนั้นวัตถุจึงถูกลบไปแล้ว)
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();
   }
}
และสุดท้าย เราต้องการ method main(): แยกมันไปไว้ในคลาสที่แยกจากMainกัน ในนั้นเราจะสร้าง object TestClassการอ้างอิง phantom และคิวสำหรับการอ้างอิง phantom หลังจากนั้นเราจะโทรหาคนเก็บขยะดูว่าจะเกิดอะไรขึ้น :)
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 !!! ref = MyPhantomReference@4554617c กำลังเรียกการรวบรวมขยะ! ทำความสะอาดลิงค์แฟนธอม! การลบวัตถุออกจากหน่วยความจำ! เราเห็นอะไรที่นี่? ทุกอย่างเกิดขึ้นตามที่เราวางแผนไว้! คลาสอ็อบเจ็กต์ของเรามีเมธอดที่ถูกแทนที่finalize()และมันถูกเรียกในขณะที่ตัวรวบรวมกำลังทำงานอยู่ ถัดไป ลิงก์ Phantom ถูกจัดReferenceQueueคิว ที่นั่นเธอมีวิธีการที่เรียกว่าclear()(ซึ่งเราทำcleanup()เพื่อเพิ่มเอาต์พุตไปยังคอนโซล) เป็นผลให้วัตถุถูกลบออกจากหน่วยความจำ ตอนนี้คุณคงได้เห็นแล้วว่ามันทำงานอย่างไร :) แน่นอนว่าคุณไม่จำเป็นต้องจำทฤษฎีทั้งหมดที่เกี่ยวข้องกับลิงค์ Phantom แต่คงจะดีถ้าคุณจำประเด็นหลักได้อย่างน้อยที่สุด ประการแรกสิ่งเหล่านี้คือลิงค์ที่อ่อนแอที่สุด สิ่งเหล่านี้จะเข้ามามีบทบาทก็ต่อเมื่อไม่มีการอ้างอิงอื่นถึงวัตถุนั้น รายการลิงก์ที่เราให้ไว้ข้างต้นนั้นเรียงลำดับตามความแข็งแกร่งจากมากไปน้อย: StrongReference-> SoftReference-> WeakReference-> PhantomReference ลิงก์ Phantom จะเข้าสู่การต่อสู้เฉพาะเมื่อไม่มีลิงก์ Strong, Soft หรือ Weak ไปยังวัตถุของเรา :) ประการ ที่สองวิธีการget()สำหรับลิงค์ phantom จะส่งกลับ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สำหรับ phantom หนึ่ง ประการที่สามพื้นที่หลักของการใช้การอ้างอิง phantom คือขั้นตอนที่ซับซ้อนในการลบวัตถุออกจากหน่วยความจำ นั่นคือทั้งหมด! :) นี่เป็นการสรุปบทเรียนของเราสำหรับวันนี้ แต่ทฤษฎีเพียงอย่างเดียวไม่สามารถพาคุณไปได้ไกล ดังนั้นถึงเวลากลับไปสู่การแก้ปัญหา! :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION