views:

1402

answers:

11

One of our programs is sometimes getting a OutOfMemory error on one users machine, but of course not when I'm testing it. I just ran it with jprofiler (on a 10 day evaluation license because I've never used it before), and filtering on our code prefix, the biggest chunk both in total size and number of instances is 8000+ instances of a particular simple class. I clicked the "Garbage Collect" button on jprofiler, and most instances of other classes of ours went away, but not these particulate ones. I ran the test again, still in the same instance, and it created 4000+ more instances of the class, but when I clicked "Garbage Collect", those went away leaving the 8000+ original ones.

These instances do get stuck into various Collections at various stages. I assume that the fact that they're not garbage collected must mean that something is holding onto a reference to one of the collections so that's holding onto a reference to the objects.

Any suggestions how I can figure out what is holding onto the reference? I'm looking for suggestions of what to look for in the code, as well as ways to find this out in jprofiler if there are.

+1  A: 

Keep an eye out for static containers. Any objects in a static container will remain as long as the class is loaded.

Edit: removed incorrect remark on WeakReference.

JesperE
A: 

If you're getting OOM errors in a garbage collected language, it usually means that there's some memory not being accounted by the collector. Maybe your objects hold non-java resources? if so, then they should have some kind of 'close' method to make sure that resource is released even if the Java object isn't collected soon enough.

Javier
+2  A: 

One obvious candidate is objects with finalisers. They can linger while their finalize method is called. They need to be collected, then finalised (usually with just a single finaliser thread) and then collected again.

Also be aware that you can get an OOME because the gc failed to collect enough memory, despite there actually being enough for the object request to be created. Otherwise performance would grind into the ground.

Tom Hawtin - tackline
+2  A: 

No silver bullet there, you have to use the profiler to identify collections that hold those unneeded objects and find the place in code where they should have been removed. As JesperE said, static collections are the first place to look at.

Jacek Szymański
+4  A: 

I would look at Collections (especially static ones) in your classes (HashMaps are a good place to start). Take this code for example:

Map<String, Object> map = new HashMap<String, Object>(); // 1 Object
String name = "test";             // 2 Objects
Object o = new Object();          // 3 Objects
map.put(name, o);                 // 3 Objects, 2 of which have 2 references to them

o = null;                         // The objects are still being
name = null;                      // referenced by the HashMap and won't be GC'd

System.gc();                      // Nothing is deleted.

Object test = map.get("test");    // Returns o
test = null;

map.remove("test");               // Now we're down to just the HashMap in memory
                                  // o, name and test can all be GC'd

As long as the HashMap or some other collection has a reference to that object it won't be garbage collected.

18Rabbit
If there wasn't any further reference to "map" after the first System.gc(), wouldn't it be garbage collected?
Paul Tomblin
Yes, if nothing else references the HashMap then it will be eligible to be GC'd.
18Rabbit
This may have changed, but doesn't the JVM ignore the system.gc call and just do it whenever it feels like it?
tloach
According to the API, "Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse." So, you kind of ask nicely and hint that you would like it done.
18Rabbit
+1  A: 

I just read an article on this, but I'm sorry I can't remember where. I think it might have been in the book "Effective Java". If I find the reference, I'll update my answer.

The two important lessons it outlined are:

1) Final methods tell the gc what to do when it culls the object, but it doesn't ask it to do so, nor is there a way to demand that it does.

2) The modern-day equivalent of the "memory leak" in unmanaged memory environments, is the forgotten references. If you don't set all references to an object to null when you're done with it, the object will never be culled. This is most important when implementing your own kind of Collection, or your own wrapper that manages a Collection. If you have a pool or a stack or a queue, and you don't set the bucket to null when you "remove" an object from the collection, the bucket that object was in will keep that object alive until that bucket is set to refer to another object.

disclaimer: I know other answers mentioned this, but I'm trying to offer more detail.

dj_segfault
I would think that you don't have to null out the reference to an object if it goes out of scope.
Paul Tomblin
In p. 24 of Bloch "Effective Java" Item 6: Eliminate obsolete object references, there is an example of badly implemented Stack where its elements are cached in Object[] elements, but they are never nulled from the elements array when pop() is called.
eed3si9n
+7  A: 

Try Eclipse Memory Analyzer. It will show you for each object how it is connected to a GC root - an object that is not garbage collected because it is held by the JVM.

See http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/ for more information on how Eclipse MAT works.

Tom
+7  A: 

Dump the heap and inspect it.

I'm sure there's more than one way to do this, but here is a simple one. This description is for MS Windows, but similar steps can be taken on other operating systems.

  1. Install the JDK if you don't already have it. It comes with a bunch of neat tools.
  2. Start the application.
  3. Open task manager and find the process id (PID) for java.exe (or whatever executable you are using). If the PID's aren't shown by default, use View > Select Columns... to add them.
  4. Dump the heap using jmap.
  5. Start the jhat server on the file you generated and open your browser to http://localhost:7000 (the default port is 7000). Now you can browse the type you're interested in and information like the number of instances, what has references to them, etcetera.

Here is an example:

C:\dump>jmap -dump:format=b,file=heap.bin 3552

C:\dump>jhat heap.bin
Reading from heap.bin...
Dump file created Tue Sep 30 19:46:23 BST 2008
Snapshot read, resolving...
Resolving 35484 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

To interpret this, it is useful to understand some of the array type nomenclature Java uses - like knowing that class [Ljava.lang.Object; really means an object of type Object[].

McDowell
+1  A: 
anjanb
+1  A: 

Collections was already mentioned. Another hard-to-find location is if you use multiple ClassLoaders, as the old classloader may be unable to be garbage collected until all references have gone.

Also check statics - these are nasty. Logging frameworks can keep things open which may keep references in custom appenders.

Did you resolve the problem?

Thorbjørn Ravn Andersen
No, I got laid off. So it's not my problem any more.
Paul Tomblin
+1  A: 

Some suggestions:

  • Unlimited maps used as caches, especially when static
  • ThreadLocals in server apps, because the threads usually do not die, so the ThreadLocal is not freed
  • Interning strings (Strings.intern()), which results in a pile of Strings in the PermSpace
ReneS