JEP-149 looks at dynamic memory footprint reductions in Java 8.
This CR covers the "Class Instance Size Reduction" project of that JEP.
In Java 8, using a 32-bit example, a java.lang.Class instance is 112 bytes consisting of:
- 8 byte object header
- 20 declared fields (mostly references, some int)
- 5 injected fields (3 references, 2 ints)
That gives: 8 + (20*4) +(5*4) = 108 bytes. But as we need 8-byte alignment that increases to 112 bytes.
Nine of the reference fields are to SoftReferences (null if not used) that hold cached reflection data (declared methods, fields, constructors etc), all of which must be cleared upon class redefinition. If reflection is not used then these fields are wasting space. There are a number of fields related to annotations, and again if unused these are wasting space - however as JSR-308 is doing a lot of work on annotation types it was decided to leave the annotation related fields alone. There are two further fields (cachedConstructor and newInstanceCallerCache) that pertain to use of the newInstance() method. The very existence of these fields suggests that newInstance is a more common reflection construct to support. Further these are direct references not soft-references and they are not cleared upon class redefinition (arguably a bug), and so for those reasons we also exclude those fields from the present changes.
The original proposal simply moved all the reflection caching soft-references into a separate helper object. That proposal was sent for review here:
and it was noted by Brian Goetz that additional savings could be made if we held one soft-reference to the helper object rather than a direct reference to a helper containing soft-references.
Peter Levart stumbled into this when he was devising solutions to an annotations processing synchronization bottleneck reported by Alexander Knoller:
After narrowing the scope to the reflection objects the discussion continued here:
With Peter's final proposed patch here:
In this design we only keep a soft-reference to a ReflectionData object which in turn holds the original fields (but directly) for the cached reflection objects. The ReflectionData object is cleared upon class redefinition and each ReflectionData instance tracks the class redefinition count at the time it was created. This in turn required a seperate redefinition count to be maintained for the annotations processing. As a result we have the following layout changes:
- 8 reference fields moved (the reflection caches)
- 1 int moved (the class redefinition count)
- 1 reference added (for the SoftReference to the ReflectionData)
- 1 int added (the class redefinition count for annotations)
This is a saving of 7 reference fields ie. 28 bytes, resulting in a new Class instance size of 80 bytes. This saves a further 4 bytes due to the fields being 8-byte aligned without any need for padding. So overall we save 32 bytes per class instance.
The ReflectionData instance itself consumes 48 bytes, while a SoftReference consumes 32 bytes.
For classes that don't use reflection this is an obvious win of 32 bytes per class. For classes that use all 8 reflection caches this is also a win as we save 7 SoftReferences ie 224 bytes.
For classes that only use one cached reflection object, however, there is a space penalty. The existing layout would consume 112 bytes for the Class instance, plus 32 bytes for the SoftReference to hold the cached array. A total of 144 bytes. The new layout consumes 80 bytes for the Class instance, 32 bytes for the SoftReference to the ReflectionData, and 48 bytes for the ReflectionData instance: a total of 160 bytes. Hence we have a 16 byte space penalty if only one reflection cache is needed. Note that if two reflection caches are used then we are again in front as the new scheme requires no further allocations, where the old would add a second SoftReference at 32-bytes, thus giving the new scheme a 16 byte advantage.