CodeGym/Blog Java/Ngẫu nhiên/Tài liệu tham khảo Phantom trong Java

Tài liệu tham khảo Phantom trong Java

Xuất bản trong nhóm
CHÀO! Trong phần thảo luận hôm nay, chúng ta sẽ nói chi tiết về "tham chiếu ảo" (PhantomReference) trong Java. Những loại tài liệu tham khảo này là gì? Tại sao chúng được gọi là "tham chiếu ảo"? Chúng được sử dụng như thế nào? Như bạn sẽ nhớ lại, Java có 4 loại tham chiếu:
  1. StrongReference (các tham chiếu thông thường mà chúng ta tạo khi tạo một đối tượng):

    Cat cat = new Cat()

    Trong ví dụ này, cat là một tham chiếu mạnh.

  2. SoftReference (tham chiếu mềm). Chúng tôi đã có một tiết học về những dẫn chứng như vậy.

  3. WeakReference (tham chiếu yếu). Cũng có một bài học về chúng ở đây .

  4. PhantomReference (tham chiếu ảo).

Ba loại cuối cùng là các loại chung với các tham số loại (ví dụ: SoftReference<Integer> , WeakReference<MyClass> ). Các lớp SoftReference , WeakReferencePhantomReference được kế thừa từ lớp Reference . Các phương thức quan trọng nhất khi làm việc với các lớp này là:
  • get() — trả về đối tượng được tham chiếu;

  • clear() — xóa tham chiếu đến đối tượng.

Bạn nhớ những phương pháp này từ các bài học về SoftReferenceWeakReference . Điều quan trọng cần nhớ là chúng hoạt động khác nhau với các loại tham chiếu khác nhau. Hôm nay chúng ta sẽ không đi sâu vào ba loại đầu tiên. Thay vào đó, chúng ta sẽ nói về tài liệu tham khảo ảo. Chúng tôi sẽ đề cập đến các loại tham chiếu khác, nhưng chỉ liên quan đến cách chúng khác với tham chiếu ảo. Đi nào! :) Để bắt đầu, tại sao chúng ta cần tài liệu tham khảo ảo? Như bạn đã biết, trình thu gom rác (gc) giải phóng bộ nhớ được sử dụng bởi các đối tượng Java không cần thiết. Bộ sưu tập xóa một đối tượng trong hai lần "vượt qua". Trong lần đầu tiên, nó chỉ xem xét các đối tượng và nếu cần, đánh dấu chúng là "không cần thiết" (có nghĩa là "cần xóa"). Nếufinalize()phương thức đã được ghi đè cho đối tượng, nó được gọi. Hoặc có thể nó không được gọi - tất cả chỉ phụ thuộc vào việc bạn có may mắn hay không. Bạn có thể nhớ điều đó finalize()hay thay đổi :) Trong lượt thứ hai của trình thu gom rác, đối tượng bị xóa và bộ nhớ được giải phóng. Hành vi không thể đoán trước của bộ thu gom rác tạo ra một số vấn đề cho chúng tôi. Chúng tôi không biết chính xác khi nào bộ thu gom rác sẽ bắt đầu chạy. Chúng tôi không biết liệu finalize()phương thức sẽ được gọi hay không. Ngoài ra, một tham chiếu mạnh đến một đối tượng có thể được tạo trong khi finalize()phương thức của nó đang được thực thi, trong trường hợp đó, đối tượng sẽ không bị xóa. Đối với các chương trình yêu cầu nhiều về bộ nhớ khả dụng, điều này có thể dễ dàng dẫn đến lỗi OutOfMemoryError. Tất cả điều này thúc đẩy chúng tôi sử dụng tài liệu tham khảo ảo. Thực tế là điều này thay đổi hành vi của người thu gom rác. Nếu đối tượng chỉ có các tham chiếu ảo, thì:
  • phương thức finalize() của nó được gọi (nếu nó bị ghi đè)

  • nếu không có gì thay đổi sau khi phương thức finalize() kết thúc và đối tượng vẫn có thể bị xóa, thì tham chiếu ảo tới đối tượng sẽ được đặt trong một hàng đợi đặc biệt: ReferenceQueue .

Điều quan trọng nhất cần hiểu khi làm việc với tham chiếu ảo là đối tượng không bị xóa khỏi bộ nhớ cho đến khi tham chiếu ảo của nó nằm trong hàng đợi này. Nó sẽ chỉ bị xóa sau khi phương thức clear() được gọi trên tham chiếu ảo. Hãy xem một ví dụ. Đầu tiên, chúng ta sẽ tạo một lớp kiểm tra sẽ lưu trữ một số loại dữ liệu.
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");
   }
}
Khi chúng tôi tạo các đối tượng, chúng tôi sẽ cố ý cung cấp cho chúng một "tải" lớn (bằng cách thêm 50 triệu ký tự "x" vào mỗi đối tượng) để chiếm nhiều bộ nhớ hơn. Ngoài ra, chúng tôi ghi đè phương thức finalize() để thấy rằng nó đang chạy. Tiếp theo, chúng ta cần một lớp kế thừa từ PhantomReference . Tại sao chúng ta cần một lớp học như vậy? Tất cả đều đơn giản. Điều này sẽ cho phép chúng ta thêm logic bổ sung vào phương thức clear() để xác minh rằng tham chiếu ma thực sự đã bị xóa (có nghĩa là đối tượng đã bị xóa).
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();
   }
}
Tiếp theo, chúng ta cần một luồng riêng biệt sẽ đợi bộ thu gom rác thực hiện công việc của nó và các liên kết ảo sẽ xuất hiện trong ReferenceQueue của chúng ta . Ngay khi một tham chiếu như vậy kết thúc trong hàng đợi, phương thức cleanup() được gọi trên đó:
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();
   }
}
Và cuối cùng, chúng ta cần phương thức main() , mà chúng ta sẽ đặt nó trong một lớp Main riêng biệt . Trong phương thức đó, chúng ta sẽ tạo một đối tượng TestClass , một tham chiếu ảo tới nó và một hàng đợi cho các tham chiếu ảo. Sau đó, chúng tôi sẽ gọi người thu gom rác và xem điều gì sẽ xảy ra :)
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();
   }
}
Đầu ra bảng điều khiển:
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!
Chúng ta thấy gì ở đây? Mọi thứ đã xảy ra như chúng ta đã lên kế hoạch! Phương thức finalize() của đối tượng của chúng ta bị ghi đè và nó được gọi trong khi trình thu gom rác đang chạy. Tiếp theo, tham chiếu ảo được đưa vào ReferenceQueue . Khi ở đó, phương thức clear() của nó được gọi (trong đó chúng ta gọi là cleanup() để xuất ra bàn điều khiển). Cuối cùng, đối tượng đã bị xóa khỏi bộ nhớ. Bây giờ bạn đã thấy chính xác cách thức hoạt động của nó :) Tất nhiên, bạn không cần phải ghi nhớ tất cả lý thuyết về tham chiếu ảo. Nhưng sẽ tốt hơn nếu bạn nhớ ít nhất những điểm chính. Đầu tiên, đây là những tài liệu tham khảo yếu nhất trong tất cả. Chúng chỉ phát huy tác dụng khi không còn tham chiếu nào khác đến đối tượng. Danh sách các tham chiếu mà chúng tôi đưa ra ở trên được sắp xếp theo thứ tự giảm dần từ mạnh nhất đến yếu nhất: StrongReference -> SoftReference -> WeakReference -> PhantomReference Một tham chiếu ảo chỉ tham gia trận chiến khi không có tham chiếu mạnh, mềm hoặc yếu nào cho đối tượng của chúng ta: ) Thứ hai, phương thức get() luôn trả về null đối với tham chiếu ảo. Dưới đây là một ví dụ đơn giản nơi chúng tôi tạo ba loại tham chiếu khác nhau cho ba loại ô tô khác nhau:
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());

   }
}
Đầu ra bảng điều khiển:
Sedan@4554617c
HybridAuto@74a14482
null
Phương thức get() trả về các đối tượng hoàn toàn bình thường cho tham chiếu mềm và yếu, nhưng nó trả về null cho tham chiếu ma. Thứ ba, các tham chiếu ma chủ yếu được sử dụng trong các thủ tục phức tạp để xóa các đối tượng khỏi bộ nhớ. Đó là nó! :) Điều đó kết thúc bài học của chúng tôi ngày hôm nay. Nhưng bạn không thể tiến xa chỉ dựa trên lý thuyết, vì vậy đã đến lúc quay lại giải quyết các nhiệm vụ! :)
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào