SoftReference incorrect javadoc?

Per Liden per.liden at oracle.com
Wed Apr 17 10:22:53 UTC 2019


Hi Michael,

On 04/17/2019 03:59 AM, Michael Pollmeier wrote:
> Hi Per,
> 
> While testing different JVMs I realized that it's fixed in openjdk 11,
> e.g. openjdk version "11.0.1" 2018-10-16 LTS (zulu build), maybe by this
> commit: https://hg.openjdk.java.net/jdk/jdk/rev/6464882498b5

That bug only affected ZGC (which was introduced in 11), so it didn't 
change the behavior of any other GC.

> That's great to know, but is it worth updating the javadocs of older
> versions?
> 
> To reproduce it I attached a simple SoftRefTest.java to easily reproduce
> it. It allocates (only) softly referenced objects that occupy some heap,
> occasionally printing counts (instantiated, finalized, free heap).

Thanks for the test. The problem with the test is that you have a 
finalize() function on Instance. This means the object will be kept 
around until it has been finalized. So even if the SoftReference 
referring to it is cleared, the memory will not be freed until the 
object also has been finalized. In your test, you're simply creating too 
many objects, and the Finalizer thread is simply unable to keep up 
finalizing them at the same pace as they are created, so the heap fills up.

I adjusted your test a bit, and removed the use of finalize(). You can 
run this test forever without running into OOMEs. By having the same 
thread that creates the objects also poll the reference queue, the test 
will automatically be throttled. If SoftReferences weren't cleared as 
they should, this test would OOME pretty fast.

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;

public class SoftRefTest2 {
   public static void main(String[] args) throws Exception {
     final ReferenceQueue<Instance> queue = new ReferenceQueue<>();
     final HashMap<Object, SoftReference<Instance>> instances = new 
HashMap<>();
     long createdCount = 0;
     long clearedCount = 0;

     for (;;) {
       SoftReference<Instance> softref = new SoftReference<Instance>(new 
Instance(), queue);
       instances.put(softref, softref);
       if (++createdCount % 100000 == 0) {
         System.out.println(createdCount + " instances created; free=" + 
Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
       }

       // Drain queue
       Reference<?> ref;
       while ((ref = queue.poll()) != null) {
           instances.remove(ref);
           if (++clearedCount % 100000 == 0) {
               System.out.println(clearedCount + " instances cleared");
           }
       }
     }
   }

   static class Instance {
     static int finalizedCount = 0;
     String[] occupySomeHeap = new String[50];
   }
}


cheers,
Per

> 
> Usage:
> javac SoftRefTest.java
> java -Xms256m -Xmx256m SoftRefTest
> 
> Output:
> 100000 instances created; free=212M
> 200000 instances created; free=181M
> 300000 instances created; free=152M
> 400000 instances created; free=121M
> 500000 instances created; free=93M
> 600000 instances created; free=61M
> 700000 instances created; free=33M
> Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
>          at Instance.<init>(SoftRefTest.java:27)
>          at SoftRefTest.main(SoftRefTest.java:12)
> 
> Interpretation:
> It doesn't free any of the only softly referenced objects, resulting in
> an OutOfMemoryError, contradicting the 'guarantee' in the javadoc.
> 
> Workaround: if you additionally configure
> `-XX:SoftRefLRUPolicyMSPerMB=0`, it finalizes them and doesn't run out
> of memory.
> 
> Tested with:
> openjdk version "1.8.0_212"
> java version "1.8.0_144" (oracle)
> openjdk version "9.0.4.1" (zulu build)
> openjdk version "10.0.2" 2018-07-17  (zulu build)
> 
> Cheers
> Michael
> 
> 
> On 16/04/19 6:27 pm, Per Liden wrote:
>> Hi Michael,
>>
>> On 4/16/19 4:19 AM, David Holmes wrote:
>>> Hi Michael,
>>>
>>> Re-directing to core-libs-dev and hotspot-gc-dev.
>>>
>>> Thanks,
>>> David
>>>
>>> On 16/04/2019 12:14 pm, Michael Pollmeier wrote:
>>>> Quoting
>>>> https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ref/SoftReference.html
>>>>
>>>>
>>>>
>>>>> All soft references to softly-reachable objects are guaranteed to have
>>>> been cleared before the virtual machine throws an OutOfMemoryError
>>>>
>>>> That statement was true when soft references were first introduced in
>>>> java 1.2, but from java 1.3.1 the jvm property
>>>> `-XX:SoftRefLRUPolicyMSPerMB` was introduced.
>>
>> That statement is still true. When the GC gets into a situation where it
>> is having trouble satisfying an allocation, then SoftRefLRUPolicyMSPerMB
>> will be ignored and all soft references will be cleared, before the GC
>> gives up and throws an OOME.
>>
>>>> It defaults to 1000 (milliseconds), meaning that if there’s only 10MB
>>>> available heap, the garbage collector will free references that have
>>>> been used more than 10s ago. I.e. everything else (including young
>>>> softly reachable objects) will *not* be freed, leading to an
>>>> OutOfMemoryError, contradicting the above quoted 'guarantee'.
>>>>
>>>> That's also the behaviour I observed on various JREs. Would you agree,
>>>> i.e. should I propose an updated doc?
>>
>> Could you elaborate on what kind of test you did to come to this
>> conclusion? Preferably provide a re-producer. In OOME situations, if a
>> SoftReference isn't cleared, it is typically because you have
>> unknowingly made it strongly reachable.
>>
>> cheers,
>> Per
>>


More information about the hotspot-gc-dev mailing list