views:

123

answers:

2

Hi friends,

I'm having problem with some framework API calling BufferedImage.getGraphics() method and thus causing memory leak. What this method does is that it always calls BufferedImage.createGraphics(). On a windows machine, createGraphics() is handled by Win32GraphicsEnvironment which keeps a listeners list inside its field displayChanger. When I call getGraphics on my BufferedImage someChart, someChart's SurfaceManager(which retains a reference to someChart) is added to the listeners map in Win32GraphicsEnvironment, preventing someChart to be garbage collected. Nothing afterwards removes someChart's SurfaceManager from the listeners map.

In general, the summarized path stopping a BufferedImage from being garbage collected, once getGraphics is called, is as follows:

GC Root -> localGraphicsEnvironment(Win32GraphicsEnvironment) -> displayChanger(SunDisplayChanger) -> listeners(Map) -> key(D3DChachingSurfaceManager) -> bImg(BufferedImage)

I could have changed the framework's code so that after every called to BufferedImage.getGraphics(), I keep a reference to the BufferedImage's SurfaceManager. Then, I get hold of localGraphicsEnvironment, cast it to Win32GraphicsEnvironment, then call removeDisplayChangedListener() using the reference to the BufferedImage's SurfaceManager. But I don't think this is a proper way to solve the problem.

Could someone please help me with this issue? Thanks a lot!


MORE DETAILS AND FINDINGS

The component I'm trying to add to my UI is makes calls to BufferedImage.getGraphics() every time it is repainted. As a result, the number of garbage kept by displayChanger(inside SunGraphicsEnvironment) should grow as the component gets repainted.

However, things a behaving weirdly enough:

when I counted my actions on my UI which would surely trigger repaint, then check the number of garbage listeners inside displayChanger against my count, they don't match up. (eg. There were 8 listeners before my clicks, and I made 60 clicks. After all, there are only 18 listeners.)

On the other hand, if I turn on the breakpoint, and step into the process of adding things to displayListeners, every single click resulted in a new entry in displayListeners. And thus, every BufferedImage held by displayListeners become garbage.

I considered the possibility of SurfaceManager, which is used as the key for displayListeners, may be shared or reused, yet my experiment ruled out this possibility. I also considered caching and I deliberately prevented caching from happening by making every call to repaint unique. Still, I have no clue how this could happen and how to solve the leak.

A: 

Try to call flush() when you don't need your image any more.

Roman
I tried, but it didn't work.
+1  A: 

After rendering the BufferedImage, you should invoke dispose() on the graphics context returned by createGraphics(). Here's an example and a list of similar methods.

Addendum: This seems like an object leak called packratting; the listener mismatch sounds like an artifact of using the debugger. You might get some ideas from the article Plugging memory leaks with soft references, by Brian Goetz.

trashgod
Sorry I didn't mention that dispose() method was called, but it does not release the image because a reference is still held by displayChanger. I even tried to get hold of the image's surface manager and call flush() hoping to destroy the manager. It didn't work.
@user359202: I wonder about the font cache in `SunGraphicsEnvironment`; I've elaborated above.
trashgod
Sounds really close. Could you elaborate more about the font cache in SunGraphicsEnvironment and how this may relate to my problem? There's little information available on this part of java I could find.
@user359202: Just a guess, but if you call `createCompositeFonts()`, the `altNameCache` `Map` may cause the kind of object loitering mentioned in the Goetz article.
trashgod