JavaRush /Blog Java /Random-VI /PhantomReference trong Java

PhantomReference trong Java

Xuất bản trong nhóm
Xin chào! Trong bài học hôm nay chúng ta sẽ nói chi tiết về Phantom References trong Java. Đây là những loại liên kết gì, tại sao chúng được gọi là “ảo” và cách sử dụng chúng như thế nào? Như bạn còn nhớ, có 4 loại liên kết trong Java:
  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()

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

  2. SoftReference (tham chiếu mềm). Chúng tôi đã có một bài giảng về những liên kết này.

  3. WeakReference (tham chiếu yếu). Ở đây cũng có một bài giảng về họ .

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

Các đối tượng thuộc ba loại cuối cùng được nhập vào (ví dụ: SoftReference<Integer> , WeakReference<MyClass> ). Các lớp SoftReferenceWeakReferencekế PhantomReferencethừ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 mà liên kết này đề cập đến;

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

Bạn nhớ những phương pháp này từ bài giảng 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 liên kết khác nhau. Hôm nay chúng ta sẽ không xem xét chi tiết ba loại đầu tiên mà sẽ nói về các liên kết ảo. Chúng tôi cũng sẽ đề cập đến các loại liên kết khác, nhưng chỉ ở phần chúng tôi sẽ nói về việc các liên kết ảo khác với chúng như thế nào. Đi! :) Hãy bắt đầu với lý do tại sao chúng ta cần liên kết ảo ngay từ đầu. Như bạn đã biết, trình thu gom rác (Garbage Collector hay gc) có nhiệm vụ giải phóng bộ nhớ khỏi các đối tượng Java không cần thiết . Trình thu thập sẽ loại bỏ đối tượng theo hai lần "lượt". Ở 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 và cần xóa”. Nếu đối tượng này có một phương thức bị ghi đè finalize()thì nó sẽ được gọi. Hoặc nó không được gọi - tùy thuộc vào vận may của bạn. Bạn có thể nhớ rằng đây finalize()là một điều hay thay đổi :) Trong lần chuyển thứ hai của bộ sưu tập, đối tượng sẽ bị xóa và bộ nhớ được giải phóng. Hành vi không thể đoán trước này của người 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 trình thu gom rác sẽ bắt đầu hoạt động. Chúng tôi không biết liệu phương thức này có được gọi hay không finalize(). Ngoài ra, trong quá trình hoạt động, finalize()một liên kết mạnh mẽ đến đối tượng có thể được tạo và sau đó nó sẽ không bị xóa. Trên các hệ thống ngốn bộ nhớ, điều này có thể dễ dàng dẫn đến OutOfMemoryError. Tất cả điều này thúc đẩy chúng tôi hướng tới việc sử dụng các liên kết ảo . Vấn đề là điều này thay đổi hành vi của người thu gom rác. Nếu chỉ còn lại các liên kết ảo cho đối tượng thì nó có:
  • phương thức được gọi finalize()(nếu nó bị ghi đè);

  • nếu sau khi làm việc finalize()không có gì thay đổi và đối tượng vẫn có thể bị xóa, một tham chiếu ảo đến đố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 các tham chiếu ảo là một đối tượng không bị xóa khỏi bộ nhớ miễn là tham chiếu ảo của nó nằm trong hàng đợi này . Nó sẽ chỉ bị xóa sau liên kết ảo clear(). Hãy xem một ví dụ. Đầu tiên, hãy tạo một lớp thử nghiệm sẽ lưu trữ một số 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("У an object TestClass вызван метод finalize!!!");
   }
}
Chúng tôi cố tình “tải” dữ liệu vào các đối tượng khi tạo chúng (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 đặc biệt ghi đè phương thức đó finalize()để xem nó có hoạt động hay không. Tiếp theo chúng ta cần một lớp sẽ kế thừa từ PhantomReference. Tại sao chúng ta cần một lớp học như vậy? Nó đơn giản. Bằng cách này, chúng ta có thể thêm logic bổ sung vào phương thức clear()để thấy rằng tham chiếu ảo đã thực sự bị xóa (và do đó đố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("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Tiếp theo, chúng ta sẽ cần một luồng riêng để chờ trình thu gom rác thực hiện công việc của nó và ReferenceQueuecác liên kết ảo sẽ xuất hiện trong hàng đợi của chúng ta. Ngay khi một liên kết như vậy được đưa vào hàng đợi, phương thức của nó sẽ được gọi 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();
   }
}
Và cuối cùng, chúng ta cần một phương thức main(): hãy đặt nó vào một lớp riêng Main. Trong đó, chúng ta sẽ tạo một đối tượng TestClass, một tham chiếu ảo đến nó và một hàng đợi cho các tham chiếu ảo. Sau đó chúng ta 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("Вызывается сборка мусора!");

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

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

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Đầu ra của bảng điều khiển: ref = MyPhantomReference@4554617c Chuỗi giám sát hàng đợi đã bắt đầu! Thu gom rác được gọi! Phương thức hoàn thiện đã được gọi trên đối tượng TestClass!!! ref = MyPhantomReference@4554617c Bộ sưu tập rác đang được gọi! Làm sạch liên kết ảo! Xóa một đối tượng khỏi bộ nhớ! Chúng ta thấy gì ở đây? Mọi chuyện diễn ra đúng như chúng tôi dự định! Lớp đối tượng của chúng tôi có một phương thức bị ghi đè finalize()và nó được gọi trong khi trình thu thập đang chạy. Tiếp theo, liên kết ảo đã được xếp hàng đợi ReferenceQueue. Ở đó cô ấy có một phương thức được gọi là clear()(từ đó chúng tôi đã cleanup()thêm đầu ra vào bảng điều khiển). Kết quả là đối tượng đã bị xóa khỏi bộ nhớ. Bây giờ bạn đã thấy chính xác nó hoạt động như thế nào :) Tất nhiên, bạn không cần phải ghi nhớ tất cả lý thuyết liên quan đến liên kết ảo. Nhưng sẽ tốt hơn nếu bạn nhớ được ít nhất những điểm chính. Thứ nhất , đây là những liên kết yếu nhất. Chúng chỉ phát huy tác dụng khi không có tham chiếu nào khác đến đối tượng. Danh sách các liên kết mà chúng tôi đưa ra ở trên được sắp xếp theo thứ tự độ mạnh giảm dần: StrongReference-> SoftReference-> WeakReference-> PhantomReference Liên kết ảo sẽ chỉ tham gia vào trận chiến khi không có liên kết Mạnh, Mềm hoặc Yếu nào đến đối tượng của chúng tôi :) Thứ hai , phương pháp get()đối với liên kết ảo luôn trả về null. Đây là một ví dụ đơn giản trong đó chúng tôi tạo ba loại liên kết 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 của bảng điều khiển: Sedan@4554617c HybridAuto@74a14482 null Phương thức get()trả về các đối tượng khá bình thường cho liên kết mềm và liên kết yếu, nhưng trả về nullđối tượng ảo. Thứ ba , lĩnh vực chính của việc sử dụng tham chiếu ảo là các thủ tục phức tạp để xóa đối tượng khỏi bộ nhớ. Đó là tất cả! :) Đến đây là bài học hôm nay của chúng ta đã kết thúc. Nhưng chỉ lý thuyết thôi sẽ không giúp bạn tiến xa được, vì vậy đã đến lúc quay lại giải quyết vấn đề! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION