 |
Leaking Evil
Posted by enicholas on April 26, 2006 at 04:39 PM | Comments (15)
Memory leaks in Java are fortunately pretty rare, and when they do exist, off-the-shelf tools like JProfiler and OptimizeIt normally make short work of them. So when I discovered that an application I'm working on had a memory leak -- a BIG one -- I wasn't too worried. At first. Should just be a simple matter of spending some quality time with a profiler, shouldn't it?
This particular leak, though, seemed to defy all effort to actually catch it in the act. It never happened while I was profiling it, and for that matter it seemed to never happen while I was actually paying attention to the application. I would spent two hours unsuccessfully trying to make the leak happen, walk away from my computer for ten minutes, and come back to find an additional five megabytes of memory gone. And when I tried the same thing (so I thought) in a profiler, it didn't happen.
As it was such a big leak, megabytes at a time, I knew it had to be image-related -- there just isn't anything else that leaks in such big chunks, and this particular application indeed processes gigantic images. I carefully checked my usage of all such images, and never found anything the least bit problematic. The bug was a ghost, striking only when my back was turned.
After a couple days of dedicated hunting, I finally managed to catch the leak in a profiler. Sure enough, it was a five-megabyte int[] which belonged to an Image. But, curiously, it wasn't my image -- it belonged to RepaintManager. I felt a sinking sensation in the pit of my stomach as I realized it was a double buffer image.
Ten seconds later, I was looking at Bug 6209673: Memory leak repainting Swing UI caused by non-java process using 3D graphics. The summary is misleading, as it has nothing to do with 3D graphics. What actually happens is that any time your computer changes screen resolution (and therefore GraphicsConfiguration) and a paint occurs, the RepaintManager allocates a new double buffer for the new GraphicsConfiguration. And keeps the old one.
Other than the obvious "manually changing screen resolution" mechanism of doing it, there are a lot of other things that will trigger this bug. Screen savers or games might change the resolution. Hibernating your system will generally result in a new GraphicsConfiguration, so a Swing program running on Java 5.0 can leak megabytes of memory every time the system is hibernated. In some cases you can defend yourself by never repainting in the background: by never painting while the screen saver or game's GraphicsConfiguration is in effect, you prevent Java from allocating a new buffer, but this isn't always practical and doesn't defend against all occurrences of the bug.
In my case, I'm on a laptop, so hibernation was the culprit. That is also the reason I could never get it to happen while I was actively looking for it -- why would I hibernate my laptop while I'm in the middle of hunting for a bug?
The workaround (subclassing RepaintManager and changing the caching code in getVolatileOffscreenBuffer) was straightforward; the real difficulty was in tracking the bug down, because by its very nature it doesn't happen while you're actively looking for it. Since this bug is pure evil to find, can affect any Swing program, usually leaks five or more megabytes at a time, and has a very misleading summary, I think it's important to be aware of it. Hopefully you'll at least figure it out faster than I did.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
hmm, Ive seen this bug report over and over but haven't read it yet. This does sound wicked. I'll have to see if I can write a collab patch for it someday.
leouser
Posted by: leouser on April 26, 2006 at 06:11 PM
-
I've recently noticed that netbeans runs fairly well on my laptop but it seemed to leak memory over the course of a day or two. I decided that it didn't really like been hibernated but didn't think about it too much. Now it seems that this might have been the bug. I hope this gets fixed in a 5.0 update rather than being rolled into Mustang.
Posted by: hopeless on April 27, 2006 at 07:31 AM
-
it does look like setDoubleBufferMaximumSize will clear out the volatile image cache, so maybe the user can use that to work around the bug.
leouser
Posted by: leouser on April 27, 2006 at 07:38 AM
-
I saw setDoubleBufferMaximumSize suggested as a workaround, but it didn't fix the leak for me.
Posted by: enicholas on April 27, 2006 at 07:42 AM
-
Hi Ethan,
Yep, this is a tricky one. In all honesty we haven't exactly rushed to fix this because display changes don't happen all that often, and this is no longer an issue in 1.6 because of gray rect changes. None-the-less it is a *big* leak when it happens, and with your blog we now have added incentive to fix and backport it;)
Thanks for the prod, we need it some times;)
-Scott
Posted by: zixle on April 27, 2006 at 07:47 AM
-
@enicholas
How did you call that method? From appearances if you call it with a smaller Dimension than the one stashed it will remove that GraphicsConfiguration/VolatileImage.
leouser
Posted by: leouser on April 27, 2006 at 07:55 AM
-
leouser: I called it with (0, 0) and then restored it to its previous size -- and the leak remained. I agree that it should have worked, but from the comments on the bug report, I am not the only one to have seen that behavior.
Scott: Thanks for the heads-up. I figured the gray-rect fix took care of this for Mustang, but it's still a *huge* issue for any long-running Swing program running on 5.0, particularly for laptop users.
Posted by: enicholas on April 27, 2006 at 08:17 AM
-
that is truly bizare, maybe something else is at play besides holding these things in a map. I find it hard to believe the map would be holding anything after you called it with a Dimension(0,0). Another possibility is that the source Im looking at is different than the source your calling. And another is that the size image could be reporting a width and height of 0, which would not cause it to be removed.
One theory I have about keeping the invalid GraphicConfigurations in the map is that if you switch to a WeakHashMap they will go away when appropiate. But it is totally reliant upon the fact that:
a. there is another reference to it in the system.
b. that this other reference will be cleaned when the GC becomes invalid.
Another weakness with the theory, is that by relying on weakness you are losing an opportunity to do any cleanup on the VolatileImage.
leouser
Posted by: leouser on April 27, 2006 at 08:51 AM
-
I've noticed memory leaks with netbeans 5.0 as well. I run on a laptop, and walk away from my pc a few times of day. Toward the end of the day, netbeans is using about 200mb of memory. This isn't a huge problem overall for my machine since it has 2 GB, but netbeans itself is a bit slow at that point.
Posted by: frank1russo on April 27, 2006 at 08:54 AM
-
Ethan, what are the details of the overriding getVolatileOffscreenBuffer method in the subclass to RepaintManager? I need to do this asap in our Java 5 desktop apps also. tia. serge.
Posted by: sm1 on April 27, 2006 at 10:12 AM
-
Take a look at the comment posted by "kass" on the original bug report -- I spent a lot of time trying different workarounds and ended up settling on something very similar to what kass did.
Posted by: enicholas on April 27, 2006 at 10:30 AM
-
Hi Scott,
I think a display change occurs with certain (all ?) screensavers on windows XP. So this certainly can happen quite often. A large app I'm working on was leaking approximatively 100 Mb / 12 hours (an app with 3 monitors, 24h/24, every day of year : this leak was and is always a little annoying to be honest). I would truly appreciate a backport in 5.0.
Posted by: evernat on April 27, 2006 at 03:24 PM
-
Yeah, this bug is a pain because it's both hard to track down and hard to work around. Fortunately our QA folks are really on the ball and hinted that the screen saver caused the memory problems (also, since we use screens that are stretched to span 2-3 monitors, the memory leak was enormous and impossible to miss.) None of the workarounds posted on the bug seemed to work, which is why I posted my RepaintManager subclass solution there. It will limit the number of virtual screens to 3, and if you exceed that it'll just throw them all out and start again. It seems to work okay as long as you have 3 screens or less, although it tends to hold on to more memory than it has to (since it's rare to actually go up to 3 screens). It feels wrong to just throw that data out, and I don't know if there's a performance hit involved.
Anyway, I hope Sun fixes the bug but I'm not holding my breath. There are a lot worse rendering bugs that, if you count all their dependent bugs, have twice as many votes but still have been in the JVM for several years and several major versions.
Posted by: samkass on April 28, 2006 at 06:04 PM
-
Hey! That's my bug report... I'll likely be on Java 6 by the time it is fixed though. Thankfully, I'm shipping my app with a bundled JRE in most cases so when Java 6 is released I won't have to worry so much about this.
Posted by: swpalmer on April 29, 2006 at 06:36 PM
-
Hehe - sounded like a quantum wave / particle duality issue for a second there...
Posted by: android5767 on October 10, 2007 at 03:21 AM
|