openplanning

Hướng dẫn và ví dụ Java SoftReference

  1. SoftReference
  2. SoftReference(T, ReferenceQueue<? super T>
  3. Caching example

1. SoftReference

Lớp java.lang.ref.SoftReference được sử dụng để tạo ra một đối tượng bao bọc (wrap) một đối tượng khác - innerObject. Đối tượng mà nó bao bọc có thể bị Garbage Collector (GC) (Trình gom rác) loại bỏ ra khỏi bộ nhớ nếu nó không còn được sử dụng tại một nơi nào khác mạnh hơn GC và hệ thống đang cần thêm bộ nhớ.
Đối tượng được gói bên trong một WeakReference đóng vai trò như một thực khách trong một nhà hàng. Khi thực khách ăn xong họ sẵn sàng rời khỏi bàn kể cả khi tại thời điểm đó nhà hàng có rất nhiều bàn trống. SoftReference hơi khác một chút so với WeakReference, thực khách có thể ngồi lại và chỉ rời đi nếu nhà hàng không còn bàn trống hoặc quá số bàn trống nhỏ hơn một giá trị an toàn.
Về cơ bản, sẽ khó khăn hơn một chút để đưa ra một ví dụ về SoftReference và cho bạn nhìn thấy trực tiếp việc Garbage Collector loại bỏ các tham chiếu mềm ra khỏi bộ nhớ, vì chúng ta cần phải mô phỏng tình huống bộ nhớ gần đầy. Nếu việc mô phỏng không hoàn hảo chúng ta sẽ làm tràn bộ nhớ, mặc dù trước khi OutOfMemoryError được ném ra các tham chiếu mềm đã bị loại bỏ khỏi bộ nhớ.
Lời khuyên: WeakReference nên được tìm hiểu trước khi tiếp tục với SoftReference:
Khi một đối tượng bị loại bỏ ra khỏi bộ nhớ, phương thức finalize() của nó sẽ được gọi. Chú ý: Phương thức này đã bị đánh dấu là lỗi thời (deprecated) từ Java 9, tuy nhiên chúng ta vẫn có thể sử dụng nó chỉ để in ra một thông báo.
AnyObject.java
package org.o7planning.beans;
 
public class AnyObject {
    private String val;
    public AnyObject(String val) {
        this.val = val;
    }
    public String getVal() {
        return this.val;
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("I am being removed from memory");
    }
}
Ví dụ dưới đây cho thấy GC sẽ loại bỏ các tham chiếu mềm ra khỏi bộ nhớ để tránh phải ném ra OutOfMemoryError.
SoftReference_obj_ex1.java
package org.o7planning.softreference.ex;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

import org.o7planning.beans.AnyObject;

public class SoftReference_obj_ex1 {

    public static void main(String[] args) throws InterruptedException {
        // Create myAnyObject reference points to AnyObject("Obj1").
        AnyObject myAnyObject = new AnyObject("Obj1");

        // Create SoftReference object
        SoftReference<AnyObject> softRef = new SoftReference<AnyObject>(myAnyObject);

        System.out.println("softRef.get(): " + softRef.get());

        List<String> list= new ArrayList<String>();

        int i = 0;
        String s = "";
        while (true) {
            AnyObject innerObject = softRef.get();
            if (innerObject == null) {
                System.out.println("Inner object is removed by Garbage Collector");
                System.out.println("softRef.get(): " + innerObject);
                break;
            }
            i++;
            //
            s = s + " String " + i; // Throw OutOfMemoryError
            list.add(s);
            System.out.println("Create new String: " + i);
        }
    }
}
Output:
softRef.get(): org.o7planning.beans.AnyObject@5e91993f
Create new String: 1
Create new String: 2

...

Create new String: 24952
Create new String: 24953
Create new String: 24954
Exception in thread "main" I am being removed from memory
java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOfRange(Arrays.java:4030)
    at java.base/java.lang.StringLatin1.newString(StringLatin1.java:715)
    at java.base/java.lang.StringBuilder.toString(StringBuilder.java:448)
    at org.o7planning.softreference.ex.SoftReference_obj_ex1.main(SoftReference_obj_ex1.java:33)
Java cho phép bạn cấu hình khi nào GC nên loại bỏ các tham chiếu mềm ra khỏi bộ nhớ.
-XX:SoftRefLRUPolicyMSPerMB=1000
Giá trị mặc định của tham số SoftRefLRUPolicyMSPerMB là 1000 Mili giây. Điều này có nghĩa là nếu bộ nhớ HEAP chỉ còn sẵn có 10MB thì GC sẽ hoạt động để loại bỏ các tham chiếu mềm đã không được sử dụng lâu hơn 1000 mili giây.
Tài liệu Java nói rằng:
"All soft references to softly-reachable objects are guaranteed to have been freed before the virtual machine throws an OutOfMemoryError." - Tất cả các tham chiếu mềm đến các đối tượng có thể tiếp cận mềm được đảm bảo đã được giải phóng trước khi máy ảo ném ra OutOfMemoryError.
Rõ ràng nhận xét trên chỉ thực sự đúng khi "-XX:SoftRefLRUPolicyMSPerMB=0".
Một ví dụ khác tốt hơn, chúng ta cố gắng mô phỏng tình huống bộ nhớ của Java gần cạn kiệt:
SoftReference_obj_ex2.java
package org.o7planning.softreference.ex;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

import org.o7planning.beans.AnyObject;

public class SoftReference_obj_ex2 {

    public static void main(String[] args) throws InterruptedException {
        // A String has size of 1Mb.
        String oneMbString = create1MbString();

        // Create myAnyObject (~2 Mb in HEAP).
        AnyObject myAnyObject = new AnyObject(oneMbString + oneMbString);

        // Create SoftReference object
        SoftReference<AnyObject> softRef = new SoftReference<AnyObject>(myAnyObject);

        System.out.println("softRef.get(): " + softRef.get());
        myAnyObject = null;

        List<String> list = new ArrayList<String>();

        int i = 0;

        while (true) {
            i++;
            long freeMemoryInBytes = Runtime.getRuntime().freeMemory(); // in bytes
            long freeMemoryInMbs = freeMemoryInBytes / (1024 * 1024);
            System.out.println("Free memory in Mb: " + freeMemoryInMbs);

            //
            if (freeMemoryInMbs <= 10) {
                Thread.sleep(1200);
            }
            if (freeMemoryInMbs <= 2) {
                System.out.println("Done!");
                break;
            }
            System.out.println(" >> Create new String");
            String s = oneMbString + " - " + i;
            list.add(s);
        }
    }

    // Create a String has the size of 1MB.
    // 1MB = 1024KB = 1024x1024 bytes = (2^10)*(2^10) bytes = 2^20 bytes.
    private static String create1MbString() {
        String s = "A"; // 2 bytes
        for (int i = 0; i < 20; i++) {
            s = s + s;
        }
        return s;
    }
}
Output:
softRef.get(): org.o7planning.beans.AnyObject@5e91993f
Free memory in Mb: 238
 >> Create new String

...

Free memory in Mb: 16
>> Create new String
Free memory in Mb: 12
>> Create new String
Free memory in Mb: 8
>> Create new String
Free memory in Mb: 14
>> Create new String
Free memory in Mb: 10
>> Create new String
Free memory in Mb: 10
>> Create new String
Free memory in Mb: 8
>> Create new String
Free memory in Mb: 6
>> Create new String
Free memory in Mb: 4
I am being removed from memory
>> Create new String
Free memory in Mb: 2
Done!
SoftReference constructors
SoftReference(T referent)

SoftReference(T referent, ReferenceQueue<? super T> queue)
Tất cả các phương thức của SoftReference được thừa kế từ lớp cha.
// Methods inherited from parent.
public T get()  
public void clear()   
public boolean isEnqueued()   
public boolean enqueue()

2. SoftReference(T, ReferenceQueue<? super T>

Tạo một đối tượng SoftReference bao bọc đối tượng innerObject. Khi innerObject bị GC loại bỏ khỏi bộ nhớ đối tượng SoftReference này sẽ được thêm vào queue.
SoftReference(T innerObject, ReferenceQueue<? super T> queue)

3. Caching example

Đôi khi SoftReference cũng được sử dụng trong một hệ thống cache đơn giản, với các dữ liệu ít khi thay đổi. Máy ảo Java sẽ tự động loại bỏ các dữ liệu này khi nó cần thêm bộ nhớ.
Ví dụ: Một hệ thống cache lưu trữ dữ liệu của các file ảnh.
ImageCache.java
package org.o7planning.softreference.cache.ex;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

public class ImageCache {
    private static final ImageCache instance = new ImageCache();

    private Map<String, SoftReference<byte[]>> cacheMap = new HashMap<>();

    private ImageCache() {
    }

    public static ImageCache getInstance() {
        return instance;
    }

    public byte[] getImageData(String imagePath) {
        SoftReference<byte[]> value = this.cacheMap.get(imagePath);
        
        byte[] data = null;
        if(value == null || (data = value.get()) == null)  {
            data = this.readImageFromDisk(imagePath);
            this.cacheMap.put(imagePath, new SoftReference<byte[]>(data));
            System.out.println(">> Load data from disk: " + imagePath);
        } else  {  
            System.out.println("   Found data in cache: " + imagePath);
        }  
        return data;
    }

    private byte[] readImageFromDisk(String imagePath) {
        // Read from disk..
        return new byte[3];
    }
}
ImageCacheTest.java
package org.o7planning.softreference.cache.ex;

public class ImageCacheTest {

    public static void main(String[] args) throws InterruptedException {
        String[] imagePaths = new String[] { //
                "image1.png", //
                "image1.png", //
                "image2.png", //
                "image2.png", //
                "image1.png", //
                "image3.png", //
                "image3.png", //
                "image1.png", //
        };

        ImageCache cache = ImageCache.getInstance();

        for (int i = 0; i < imagePaths.length; i++) {
            byte[] data = cache.getImageData(imagePaths[i]);
            Thread.sleep(500);
        }

        System.out.println("Done!");
    }
}
Output:
>> Load data from disk: image1.png
   Found data in cache: image1.png
>> Load data from disk: image2.png
   Found data in cache: image2.png
   Found data in cache: image1.png
>> Load data from disk: image3.png
   Found data in cache: image3.png
   Found data in cache: image1.png
Done!

Java cơ bản

Show More