devstory

Le Tutoriel de Java SoftReference

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

1. SoftReference

La classe java.lang.ref.SoftReference est utilisée pour créer un objet qui enveloppe (wrap) un autre objet - innerObject. L'objet qu'il encapsule peut être supprimé de la mémoire par Garbage Collector (GC) s'il n'est plus utilisé ailleurs que par GC et que le système a besoin de plus de mémoire.
L'objet est enveloppé dans une WeakReference qui agit comme un diner dans un restaurant. Lorsque les convives ont fini de manger, ils sont prêts à quitter la table même si à ce moment-là, le restaurant a de nombreuses tables vides. SoftReference est un peu différent de WeakReference, les convives peuvent s'asseoir et ne partir que si le restaurant n'a plus de tables libres ou si le nombre de tables libres disponibles est inférieur à une valeur sûre.
En règle générale, il est plus difficile de donner un exemple de SoftReference et de vous montrer comment Garbage Collector supprime les références logicielles de la mémoire, car on doit simuler une situation de mémoire presque pleine. Si la simulation n'est pas parfaite, la mémoire sera débordée. Bien qu'avant la levée d'OutOfMemoryError, les références logicielles étaient supprimées de la mémoire.
Conseil: WeakReference doit être appris avant de continuer avec SoftReference:
Lorsqu'un objet est supprimé de la mémoire, sa méthode finalize() est convoquée. Remarque: cette méthode a été marquée comme obsolète depuis Java 9, mais on peut toujours l'utiliser uniquement pour imprimer un message.
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");
    }
}
L'exemple ci-dessous montre que GC supprimera les références logicielles de la mémoire pour éviter de générer une 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 vous permet de configurer quand GC doit supprimer les références logicielles de la mémoire.
-XX:SoftRefLRUPolicyMSPerMB=1000
La valeur par défaut du paramètre SoftRefLRUPolicyMSPerMB est de 1000 millisecondes. Cela signifie que si seulement 10MB de mémoire HEAP sont disponibles, GC s'efforcera de supprimer les références logicielles qui ont été inutilisées pendant plus de 1000 millisecondes.
Selon les documents sur Java:
«Toutes les références logicielles à des objets facilement accessibles sont garanties d'avoir été libérées avant que la machine virtuelle ne renvoie une erreur OutOfMemoryError
Il paraît que cette remarque est seulement juste lorsque "-XX:SoftRefLRUPolicyMSPerMB=0".
Un autre meilleur exemple. Essayer de simuler une situation dans laquelle la mémoire de Java est presque épuisée:
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)
Toutes les méthodes de SoftReference sont héritées de la classe parentale.
// Methods inherited from parent.
public T get()  
public void clear()   
public boolean isEnqueued()   
public boolean enqueue()

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

Créer un objet SoftReference qui enveloppe l'objet innerObject. Lorsque innerObject est supprimé de la mémoire par GC, cet objet SoftReference sera ajouté à queue.
SoftReference(T innerObject, ReferenceQueue<? super T> queue)

3. Caching example

Parfois, SoftReference est également utilisé dans un système de cache simple, où les données changent rarement. La machine virtuelle Java supprime automatiquement ces données lorsqu'elle a besoin de plus de mémoire.
Exemple: Un système de cache stocke les données des fichiers image.
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 Basic

Show More