Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BufferedImage.getGraphics() resulting in memory leak, is there a fix?

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.

like image 835
Huinan Avatar asked Jun 05 '10 13:06

Huinan


2 Answers

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.

like image 123
trashgod Avatar answered Nov 06 '22 02:11

trashgod


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

like image 1
Roman Avatar answered Nov 06 '22 02:11

Roman