United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 6448405 static HashMap cache in LineBreakMeasurer can grow wihout bounds
6448405 : static HashMap cache in LineBreakMeasurer can grow wihout bounds

Details
Type:
Bug
Submit Date:
2006-07-13
Status:
Closed
Updated Date:
2011-03-08
Project Name:
JDK
Resolved Date:
2011-03-08
Component:
client-libs
OS:
windows_xp
Sub-Component:
2d
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
5.0
Fixed Versions:
7

Related Reports
Backport:
Backport:
Backport:

Sub Tasks

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.
                                     
2006-09-15
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
                                     
2006-07-14



Hardware and Software, Engineered to Work Together