JDK-8029882 : jhat sometimes hides soft references
  • Type: Bug
  • Component: core-svc
  • Sub-Component: tools
  • Affected Version: 7u45
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: linux
  • Submitted: 2013-12-09
  • Updated: 2023-12-14
  • Resolved: 2015-05-08
Description
FULL PRODUCT VERSION :
% java -version
java version "1.7.0_45"
OpenJDK Runtime Environment (IcedTea 2.4.3) (Gentoo build 1.7.0_45-b31)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

A DESCRIPTION OF THE PROBLEM :
jhat and jmap are useful tools for debugging memory leaks. I use the "Rootset references" page to track what's keeping an object from being garbage collected. However, sometimes the information jhat shows can be misleading - it appears that an object is being held exclusively by weak references.
I think it's happening when an object has both a weak and a soft reference from the same source, and jhat finds the weak reference first and seems to ignore/hide the soft reference.

Please refer to the example program. It creates an object and puts it into a map, using a weak reference to it as the key and a soft reference to it as the value. No other references are kept, so the object hangs around for a while due to the soft reference, as can be seen from the printout.

However, when checking the rootset references to that object (the single SoftTest instance), jhat only shows the weak reference from the key and fails to show the soft reference from the value, which is the only thing that's preventing the object from being garbage collected. So it's showing an object that apparently is held only by weak references, but still survives a long series of garbage collections. Knowing the behavior of this jvm, that should be impossible.

I would like to add that this is not a contrived example, but a simplification of a problem I encountered with a web application (after undeployment). The object was a webapp classloader (indirectly referred from other objects), and the map field was one of java.io.ObjectStreamClass$Caches.localDescs and java.io.ObjectStreamClass$Caches.reflectors (posssibly both of them).


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- run the program
- dump the memory with jmap -dump:format=b,file=leak <pid>
- run jhat -J-Xmx512m leak
- go to http://localhost:7000/
- click the SoftTest class, then Instances (either exclude or include subclasses)
- click the single SoftTest instance
- go to "Reference Chains from Rootset", "Exclude weak refs" -> empty page
- click "Include weak refs" -> it shows only the weak reference from the map field


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I would actually expect to see the soft reference on the "Exclude weak refs" page, since soft references are not weak references. But at the very least, it should show it on the "Include weak refs" page.
Ideally, there should be a separate link for each type of reference (without mixing it with other types).
ACTUAL -
As explained, the "Exclude weak refs" page is empty, and on the "Include weak refs" page it only shows the weak reference from the map entry's key. All reference chains end in:

--> class mem.SoftTest (168 bytes) (static field map:)
--> java.util.HashMap@0x785003cc8 (68 bytes) (field table:)
--> [Ljava.util.HashMap$Entry;@0x785003cf8 (144 bytes) (Element 11 of [Ljava.util.HashMap$Entry;@0x785003cf8:)
--> java.util.HashMap$Entry@0x785003d48 (44 bytes) (field key:)
--> java.lang.ref.WeakReference@0x785003d68 (48 bytes) (field referent:)
--> mem.SoftTest@0x785003db0 (16 bytes)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class SoftTest {
private static Map<Reference<?>, Reference<?>> map = new HashMap<Reference<?>, Reference<?>>();

@Override
public String toString() {
return "obj";
}

public static void main(final String... args) throws Exception {
SoftTest x = new SoftTest();
map.put(new WeakReference<SoftTest>(x), new SoftReference<SoftTest>(x));
x = null;
while (true) {
Thread.sleep(1000);
System.gc();
final Entry<Reference<?>, Reference<?>> e = map.entrySet().iterator().next();
System.out.println(new Date() + ": " + e.getKey().get() + " - " + e.getValue().get());
}
}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Not really a workaround for the jhat issue, but running the code with -XX:SoftRefLRUPolicyMSPerMB=1 makes the soft references expire much faster, and that helps to verify that objects are being collected when debugging. Probably not recommended in production :)
Comments
JHAT was removed with JDK-8059039, closing this as Won't Fix.
08-05-2015

ILW=MLM=P4, Incomplete output, rare cases, use MAT instead.
16-12-2013

Not sure if this should be on core-svc or tools
10-12-2013