views:

519

answers:

6

I am currently trying to diagnose a slow memory leak in my application. The facts I have so far are as follows.

  • I have a heap dump from a 4 day run of the application.
  • This heap dump contains ~800 WeakReference objects which point to objects (all of the same type, which I will call Foo for the purposes of this question) retaining 40mb of memory.
  • Eclipse Memory Analysis Tool shows that each of the Foo objects referred to by these WeakReferences is not referred to by any other objects. My expectation is that this should make these Foo objects Weakly Reachable and thus they should be collected at the next GC.
  • Each of these Foo objects has a timestamp which shows that they were allocated over the course of the 4 day run. I also have logs during this time which confirm that Garbage Collection was happening.
  • A huge number of Foo objects are being created by my application and only a very small fraction of them are ending up in this state within the heap dump. This suggests to me that the root cause is some sort of race condition.
  • My application uses JNI to call through to a native library. The JNI code calls NewGlobalRef 4 times during start of day initialisation to get references to Java classes which it uses.

What could possibly cause these Foo classes to not be collected despite only being referenced by WeakReferences (according to Eclipse Memory Analyser Tool)?

EDIT1:

@mindas The WeakReference I am using is equivalent to the following example code.

public class FooWeakRef extends WeakReference<Foo>
{
  public long longA;
  public long longB;
  public String stringA;

  public FooWeakRef(Foo xiObject, ReferenceQueue<Foo> xiQueue)
  {
    super(xiObject, xiQueue);
  }
}

Foo does not have a finalizer and any finalizer would not be a consideration so long as the WeakRefs have not been cleared. An object is not finalizable when it is weakly reachable. See this page for details.

@kasten The weakreferences are cleared before the object is finalizable. My heap dump shows that this has not happened.

@jarnbjo I refer to the WeakReference Javadoc:

"Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references."

This suggests to me that the GC should be detecting the fact that my Foo objects are "Weakly reachable" and "At that time" clearing the weak references.

EDIT 2

@j flemm - I know that 40mb doesn't sound like much but I am worried that 40mb in 4 days means 4000mb in 100 days. All of the docs I have read suggest that objects which are weakly reachable should not hang around for several days. I am therefore interested in any other explanations about how an object could be strongly referenced without the reference showing up in a heap dump.

I am going to try allocating some large objects when some of these dangling Foo objects are present and see whether the JVM collects them. However, this test will take a couple of days to setup and complete.

EDIT 3

@jarnbjo - I understand that I have no guarantee about when the JDK will notice that an object is weakly reachable. However, I would expect that an application under heavy load for 4 days would provide enough opportunities for the GC to notice that my objects are weakly reachable. After 4 days I am strongly suspicious that the remaining weakly references objects have been leaked somehow.

EDIT 4

@j flemm - Thats really interesting! Just to clarify, are you saying that GC is happening on your app and is not clearing Soft/Weak refs? Can you give me any more details about what JVM + GC Config you are using? My app is using a memory bar at 80% of the heap to trigger GC. I was assuming that any GC of the old gen would clear Weak refs. Are you suggesting that a GC only collects Weak refs once the memory usage is above a higher threshold? Is this higher limit configurable?

EDIT 5

@j flemm - Your comment about clearing out WeakRefs before SoftRefs is consistent with the Javadoc which states: SoftRef: "Suppose that the garbage collector determines at a certain point in time that an object is softly reachable. At that time it may choose to clear atomically all soft references to that object and all soft references to any other softly-reachable objects from which that object is reachable through a chain of strong references. At the same time or at some later time it will enqueue those newly-cleared soft references that are registered with reference queues."

WeakRef: "Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues."

For clarity, are you saying that the Garbage Collector runs when your app has more than 50% free memory and in this case it does not clear WeakRefs? Why would the GC run at all when your app has >50% free memory? I think your app is probably just generating a very low amount of garbage and when the collector runs it is clearing WeakRefs but not SoftRefs.

EDIT 6

@j flemm - The other possible explanation for your app's behaviour is that the young gen is being collected but that your Weak and Soft refs are all in the old gen and are only cleared when the old gen is being collected. For my app I have stats showing that the old gen is being collected which should mean that WeakRefs get cleared.

EDIT 7

I am starting a bounty on this question. I am looking for any plausible explanations for how WeakRefs could fail to be cleared while GC is happening. If the answer is that this is impossible I would ideally like to be pointed at the appropriate bits of OpenJDK which show WeakRefs being cleared as soon as an object is determined to be weakly reachable and that weak reachability is resolved every time GC runs.

A: 

You need to clarify on what is the link between Foo and WeakReference. The case

class Wrapper<T> extends WeakReference<T> {
  private final T referent;
  public Wrapper(T referent) {
    super(t);
    this.referent = referent;
  }
}

is very different from just

class Wrapper<T> extends WeakReferece<T> {
  public Wrapper(T referent) {
    super(t);
  }
}

or its inlined version, WeakReference<Foo> wr = new WeakReference<Foo>(foo).

So I assume your case is not like I described in my first code snippet.

As you have said you are working with JNI, you might want to check if you have any unsafe finalizers. Every finalizer should have finally block calling super.finalize() and it's easy to slip.

You probably need to tell us more about the nature of your objects to offer better ideas.

mindas
I think you misspelled your constructor names, and as i understand it, his WeakReferences reference Foo objects, which do not extend WeakReference.
Christoffer Hammarström
Thanks Cristoffer - you're absolutely right!
mindas
I believe this is related to this question: http://stackoverflow.com/questions/1744533/jna-bytebuffer-not-getting-freed-and-causing-c-heap-to-run-out-of-memory
Mark E
Thanks for the interesting link but my application is garbage collecting frequently and the potential leak is in the Java heap so I think my questions is different to the one you linked.
mchr
A: 

Try SoftReference instead. Javadoc says: All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

WeakReference doesn't have such guarantees, which makes them more suitable for caches, but sometimes SoftReferences are better.

iirekm
WeakRefs are cleared before SoftRefs - the javadocs state WeakRefs are cleared as soon as the GC detects them as weakly reachable.
mchr
Read Javadoc carefully:- http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability - The order of clearing in Java: strong (normal) refs, soft refs, weak refs, phantom refs.- http://download.oracle.com/javase/1.4.2/docs/api/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.
iirekm
A: 

@iirekm No: WeakReferences are 'weaker' than SoftReferences, meaning that a WeakReference will always be garbage collected before a SoftReference.

More info in this post: http://stackoverflow.com/questions/3329691/understanding-javas-reference-classes-softreference-weakreference-and-phantom

Edit: (after reading comments) Yes surely Weak References are 'Weaker' than SoftReferences, typo. :S

Here's some use cases to throw further light on the subject:

  • SoftReference: In-memory cache (Object stays alive until VM deems that there's not enough heap mem)
  • WeakReference: Auto-clearing Listeners (Object should be cleared on next GC cycle after deemed being Weakly reachable)
  • PhantomReference: Avoiding out-of-memory errors when handling unusually large objects (When scheduled in reference queue, we know that host object is to be cleared, safe to allocate another large object). Think of it as a finalize() alternative, without the ability to bring dead objects back to life (as you potentially could with finalize)

This being said, nothing prevents the VM (please correct me if I'm wrong) to let the Weakly reachable objects stay alive as long as it is not running out of memory (as in the orig. author's case).

This is the best resource I could find on the subject: http://www.pawlan.com/monica/articles/refobjs/

Edit 2: Added "to be" in front of cleared in PhantomRef

bjornl
I think is the other way around. A SoftReference has a longer staying power than a WeakReference.
rmarimon
Check here http://stackoverflow.com/questions/299659/what-is-the-difference-between-a-soft-reference-and-a-weak-reference-in-java
rmarimon
No, people often confuse Weak and SoftReference. Check javadoc: SoftReference is guaranteed to be cleared before OutOfMemoryError, there's no such guarantee with WeakReference. SoftReference can be e.g. for listeners (object disappears -> all listeners should too), WeakReference for caches (with SoftReferences the data would disappear too often, so such cache would have little sense, thus we have only WeakHashMap, we don't have SoftHashMap in java.util).It's all summed up here: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability
iirekm
I don't think your Phantom Ref use case is correct. According to the javadoc the phantom ref is not cleared by the GC. It is enqueued after finalisation but there must be a user thread servicing the reference queue to explicitly clear the phantom references.
mchr
It is enqueued after being cleared, that's why the use of a queue is required when instantiating a phantom reference and also why a phantom reference's .get() is guaranteed to return null.
bjornl
In the javadoc http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability they are ordered from strongest to weakest, softref appearing before weakref. Does that not imply that a softreference is cleared before a weak one?
bjornl
@bjorn Yes softrefs are cleared before weakrefs.
mchr
@bjorn - The javadoc clearly states that PhantomRefs are not cleared until the user explicitly clears them. The get() method always returns null so that PhantomRefs cannot be used to resurrect an object.
mchr
@bjornl - You have not answered by question but have given some decent information and links about Reference objects in general. I am assigning you the bounty.
mchr
Sweet, thanks!! :)
bjornl
+1  A: 

You might want to check if you have leaked classloader issue. More on this topic you could find in this blog post

iYasha
A leaked classloader would show up in the heap dump and would be shown in Eclipse Memory Analysis Tool.
mchr
A: 

I am not acquainted with Java, but you may be using a generational garbage collector, which will keep your Foo and FooWeakRef objects alone (not collected) as long as

  • they passed in an older generation
  • there is enough memory to allocate new objects in younger generations

Does the log that indicates that garbage collection occurred discriminates between major and minor collections?

plodoc
Yes it does and old generation and young generation collections are both happening.
mchr
A: 

For non-believers who claim that weak references are cleared before soft references:

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


public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
    ReferenceQueue<Object> q = new ReferenceQueue<Object>();
    Map<Reference<?>, String> referenceToId = new HashMap<Reference<?>, String>();
    for(int i=0; i<100; ++i) {
        Object obj = new byte [10*1024*1024];    // 10M
        SoftReference<Object> sr = new SoftReference<Object>(obj, q);
        referenceToId.put(sr, "soft:"+i);
        WeakReference<Object> wr = new WeakReference<Object>(obj, q);
        referenceToId.put(wr, "weak:"+i);

        for(;;){
            Reference<?> ref = q.poll();
            if(ref == null) {
                break;
            }
            System.out.println("cleared reference " + referenceToId.get(ref) + ", value=" + ref.get());
        }
    }
}
}

If your run it with either -client or -server, you'll see that soft references are always cleared before weak references, which also agrees with Javadoc: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability

Typically soft/weak references are used in connection with Maps to make kinds of caches. If keys in your Map are compared with == operator, (or unoverriden .equals from Object), then it's best to use Map which operates on SoftReference keys (eg from Apache Commons) - when the object 'disappears' no other object will ever be equal in the '==' sense to the old one. If keys of your Map are compared with advanced .equals() operator, like String or Date, many other objects may match to the 'disappearing' one, so it's better to use the standard WeakHashMap.

iirekm