โค้ดยิม/จาวาบล็อก/สุ่ม/การอ้างอิง Phantom ใน Java
John Squirrels
ระดับ
San Francisco

การอ้างอิง Phantom ใน Java

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

    Cat cat = new Cat()

    ในตัวอย่างนี้catเป็นข้อมูลอ้างอิงที่ชัดเจน

  2. SoftReference (การอ้างอิงแบบอ่อน) เราได้บทเรียนเกี่ยวกับการอ้างอิงดังกล่าว

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

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

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

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

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

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

สิ่งที่สำคัญที่สุดที่ต้องเข้าใจเมื่อทำงานกับการอ้างอิงแบบ Phantom คือ อ็อบเจ็กต์จะไม่ถูกลบออกจากหน่วยความจำจนกว่าการอ้างอิงแบบ Phantom จะอยู่ในคิวนี้ มันจะถูกลบหลังจากที่ มีการเรียกใช้เมธอด clear()ในการอ้างอิงแบบ phantom เท่านั้น ลองดูตัวอย่าง ก่อนอื่น เราจะสร้างคลาสทดสอบที่จะเก็บข้อมูลบางประเภท
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("The finalize method has been called on the TestClass object");
   }
}
เมื่อเราสร้างออบเจกต์ เราจงใจให้ "โหลด" จำนวนมากแก่ออบเจ็กต์ (โดยการเพิ่มอักขระ "x" 50 ล้านตัวในแต่ละออบเจ็กต์) เพื่อให้ใช้หน่วยความจำมากขึ้น นอกจากนี้ เราลบล้างเมธอด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("Cleaning up a phantom reference! Removing an object from memory!");
       clear();
   }
}
ต่อไป เราต้องการเธรดแยกต่างหากที่จะ รอ ให้ตัวรวบรวมขยะทำงาน และลิงก์หลอนจะปรากฏในReferenceQueue ของเรา ทันทีที่การอ้างอิงดังกล่าวจบลงในคิว วิธี การล้างข้อมูล ()จะถูกเรียกใช้:
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("The thread monitoring the queue has started!");
       Reference ref = null;

       // Wait until the references appear in the queue
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Thread " + getName() + " was interrupted!");
           }
       }

       // As soon as a phantom reference appears in the queue, clean it up
       ((MyPhantomReference) ref).cleanup();
   }
}
และสุดท้าย เราต้องการ เมธอด 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("Collecting garbage!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Collecting garbage!");

       System.gc();
   }
}
เอาต์พุตคอนโซล:
ref = MyPhantomReference@4554617c
The thread monitoring the queue has started!
Collecting garbage!
The finalize method has been called on the TestClass object
ref = MyPhantomReference@4554617c
Collecting garbage!
Cleaning up a phantom reference!
Removing an object from memory!
เราเห็นอะไรที่นี่? ทุกอย่างเป็นไปตามที่เราวางแผนไว้! เมธอด Finalize()ของออบเจกต์ของเราถูกแทนที่และถูกเรียกในขณะที่ตัวรวบรวมขยะกำลังทำงาน ถัดไป การอ้างอิงหลอนถูกใส่ลงในReferenceQueue ในขณะนั้น มีการเรียกใช้เมธอด clear() (ซึ่งเราเรียกว่าcleanup()เพื่อส่งออกไปยังคอนโซล) ในที่สุดวัตถุก็ถูกลบออกจากหน่วยความจำ ตอนนี้คุณคงเห็นแล้วว่ามันทำงานอย่างไร :) แน่นอน คุณไม่จำเป็นต้องจำทฤษฎีทั้งหมดเกี่ยวกับการอ้างอิงภาพหลอน แต่จะเป็นการดีถ้าคุณจำประเด็นหลักได้อย่างน้อย ประการแรก สิ่งเหล่านี้เป็นข้อมูลอ้างอิงที่อ่อนแอที่สุดของทั้งหมด พวกเขาเข้ามาเล่นก็ต่อเมื่อไม่มีการอ้างอิงถึงวัตถุอื่น ๆ รายการอ้างอิงที่เราให้ไว้ด้านบนนั้นเรียงลำดับจากมากไปหาน้อยจากมากไปน้อย: StrongReference -> SoftReference -> WeakReference -> PhantomReference การอ้างอิง Phantom เข้าสู่การต่อสู้ก็ต่อเมื่อไม่มีการอ้างอิงที่แข็งแกร่ง นุ่มนวล หรืออ่อนแอต่อวัตถุของเรา: ) ประการที่สอง เมธอด 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()ส่งคืนออบเจกต์ธรรมดาทั้งหมดสำหรับการอ้างอิงแบบซอฟต์และอ่อนแอ แต่มันกลับเป็นค่าว่างสำหรับการอ้างอิงแบบหลอน ประการที่สาม การอ้างอิงแบบ Phantom ส่วนใหญ่จะใช้ในขั้นตอนที่ซับซ้อนสำหรับการลบออบเจกต์ออกจากหน่วยความจำ แค่นั้นแหละ! :) นั่นคือการสรุปบทเรียนของเราในวันนี้ แต่คุณไม่สามารถไปได้ไกลจากทฤษฎีเพียงอย่างเดียว ดังนั้นได้เวลากลับไปแก้ไขงานแล้ว! :)
ความคิดเห็น
  • เป็นที่นิยม
  • ใหม่
  • เก่า
คุณต้องลงชื่อเข้าใช้เพื่อแสดงความคิดเห็น
หน้านี้ยังไม่มีความคิดเห็นใด ๆ