JDK-6448405 : static HashMap cache in LineBreakMeasurer can grow wihout bounds
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-07-13
  • Updated: 2011-03-08
  • Resolved: 2011-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other JDK 6 JDK 7
5.0u16-revFixed 5.0u17Fixed 6u2Fixed 7 b03Fixed
Description
FULL PRODUCT VERSION :
java version "1.5.0_05"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_05-b05)
Java HotSpot(TM) Client VM (build 1.5.0_05-b05, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
The FontRenderContext that is given to the constructor of the LineBreakManager contains a AffineTransform object.
The 6 double values (scale/shear/transform) inside this AffineTransform object all are part of the key that is used to fill the static hashmap "SDCache" in class "sun.font.GlyphLayout".
The transform-x/y values in the AffineTransform are most of the cases different values, because different texts are painted on different points on the screen. The transform-x/y values are not needed by the LineBreakMeasurer: a string is just as long on any place on the screen.
In my app, eventually the static cache consumes 100MB of the java heap after it has been filled with 250000+ FontRenderContext objects!

In my opinion this is a slowly progressing memory leak.

OutOfMemory errors and a big windows VM size for java.exe are the result.

Solution: don't use the transforn-x/y values in the key in the hashmap.
I cleared then in the FontRenderContext before constructing the LineBreakMeasurer and the hashmap did not grow more than 3 FontRenderContext objects.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use a LineBreakMeasurer object to measure text.
Use a new LineBreakMeasurer object for every consequtive text.
Supply the FontRenderContext that you get from a Graphics2D instance to the constructor for the LineBreakMeasurer.
But first set the Graphics2D context at the translation point for where you intend to paint the text on the screen. These coordinates will be different for each text you paint (else you cannot read the texts on the screen!)

You will see the number of sun.font.GlyphLayout$SDCache objects grow in your memory profiler. All these objects are held in a static hashmap, so garbage collection does not remove them from the heap.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I would expect that caching would stay within limits.
Do not use the transform-x/y values in the key for the cache.
ACTUAL -
The transform-x/y values are used in the key for the cache and so the cache grows beyond proportions after enough time and repainting.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Moved to commments section.

REPRODUCIBILITY :
This bug can be reproduced always.

Comments
EVALUATION In the end the fix used a ConcurrentHashMap for the synchronization problem and a SoftReference to the whole map to ensure collection is possible. Also the cache code was updated to ignore any translation component so in all likelihood very few entries will be needed anyway in most apps. Also it was noted that Graphics2D.getFontRenderContext() already excluded the translation component from any FRC it returned so long as the graphics transform was a simple translation. So FRCs from this source should not have caused such an explosion anyway. The fix also made that implementation behave consistently if there was a scale etc.
15-09-2006

EVALUATION A few issues here * The translation component of the FRC transform probably should not be used here which would help. So we'd have to extract that part out * HashMap is used and updated without being synchronized Either than or use Hashtable or ConcurrentHashmap, except that what we really want (see below) is something more like WeakHashMap and there's no MT safe version of that. But the synchronisation will reduce the scaleability. * The cache should not be allowed to grow without bounds. It could be replaced with a WeakHashMap. The key would need to be referenced by the value to prevent it being freed immediately, and the value would then need to be wrapped in a WeakReference. Then when GC occurs first the SDCache instance will be freed, and then the map will free the weak key and its weak reference value Its not clear if this will have any significant impact on performance - probably not. We could also look at the work that the cache is trying to avoid and see if that can be improved on without the neede for a cache
14-07-2006