Inline classes are designed to be flattened in other data structures (objects, arrays or other inline classes). They are also user defined types, meaning that their size and alignment constraints depend on how the author defined them. This diversity in size and alignment was a big challenge for the algorithm in charge of computing field layouts, which was designed to handle a limited set of well defined types.
For prototyping purposes, a quick and simple modification of the layout algorithm was modified to support flattened fields. All flattened fields were considered to have the strongest alignment constraint (64-bits, similar to long and double), and their size was simply encoded in _nonstatic_field_size, just as regular objects. These modifications were simple, and allowed quick prototyping, but they are largely inefficient.
Many inline classes do not require to be aligned on a 64-bits boundary. Artificially forcing them to be 64-bits aligned causes waste of space in the layout.
Example:
inline class TwoBytes {
byte b0,b1;
}
class TestClass1 {
TwoBytes tb0,tb1;
}
Even if the flattened fields tb0 and tb1 have a real payload of only two bytes each, the 64-bits alignment generates the following layout:
TestClass1: field layout
@ 12 --- instance fields start ---
@ 16 "tb0" QFlatteningTest$TwoBytes;
@ 24 "tb1" QFlatteningTest$TwoBytes;
@ 32 --- instance fields end ---
@ 32 --- instance ends ---
@120 --- static fields start ---
@120 --- static fields end ---
The total size allocated for TestClass1's fields is 20 bytes, whereas the real payload is only 4 bytes.
This artificial alignment constraint also increased the size of the standalone inline class instances:
TestClass1: field layout
@ 12 --- instance fields start ---
@ 16 "tb0" QFlatteningTest$TwoBytes;
@ 24 "tb1" QFlatteningTest$TwoBytes;
@ 32 --- instance fields end ---
@ 32 --- instance ends ---
@120 --- static fields start ---
@120 --- static fields end ---
Instead of allocating fields tb0,tb1 just after the object header, the 64-bits alignment forces the layout to jump to the first 64-bits boundary (offset 16) before allocating them.
The prototyping code was largely inefficient, but there was no easy way to fix it. The field layouts are computed in the infamous ClassFileParser::layout_fields() method: a huge monolithic method with dozen of interdependent local variables. Huge re-work of this method would have been required to just introduce concepts of user-defined sizes and alignment constraint. And such changes would have make this method even bigger and even harder to read.
The proposed fix is to provide a framework to abstract layouts and elements of a layout. Then, use this framework to implement several ad hoc allocation strategies, with different goals and tradeoffs. Each allocation strategy would be much more easier to implement and to read.
The framework must be generic enough to support future layout enhancements (like support for hyper-alignment).
The abstraction for elements of a layout must be flexible enough to support both statically defined and user defined sizes and alignments.
Layout abstraction must be independent from the allocation strategies. Allocation strategies must be free to allocate fields in the order they consider best for their goals. It must be possible to implement multiple strategies instead of a single fit-all allocation code (for instance, inline classes and regular classes do not have the same goals, and should have distinct allocation strategies).
Meta-data in the JVM would also require some extensions, in addition to the new layout code, to store the new information required to compute layouts more efficiently (alignment constraints, exact sizes in bytes).
An implementation of this proposal can be found here:
http://cr.openjdk.java.net/~fparain/layout/webrev.00/
The code contains a lot of comments describing the framework, the elements of the framework, and the different allocation strategies.
With this implementation, the layout of the TestClass1 is now:
Layout of class TestClass1
|offset|kind|size|alignment|signature|name|
Instance fields:
0 RESERVED 12
12 FLATTENED 2 1 QTwoBytes; tb0
14 FLATTENED 2 1 QTwoBytes; tb1
Static fields
0 RESERVED 120
And the layout of the TwoBytes class:
Layout of class FlatteningTest$TwoBytes
|offset|kind|size|alignment|signature|name|
Instance fields:
0 RESERVED 12
12 REGULAR 1 1 B b0
13 REGULAR 1 1 B b1
Static fields
0 RESERVED 120
120 REGULAR 4 4 java/lang/Object .default
No more space wasted!
For prototyping purposes, a quick and simple modification of the layout algorithm was modified to support flattened fields. All flattened fields were considered to have the strongest alignment constraint (64-bits, similar to long and double), and their size was simply encoded in _nonstatic_field_size, just as regular objects. These modifications were simple, and allowed quick prototyping, but they are largely inefficient.
Many inline classes do not require to be aligned on a 64-bits boundary. Artificially forcing them to be 64-bits aligned causes waste of space in the layout.
Example:
inline class TwoBytes {
byte b0,b1;
}
class TestClass1 {
TwoBytes tb0,tb1;
}
Even if the flattened fields tb0 and tb1 have a real payload of only two bytes each, the 64-bits alignment generates the following layout:
TestClass1: field layout
@ 12 --- instance fields start ---
@ 16 "tb0" QFlatteningTest$TwoBytes;
@ 24 "tb1" QFlatteningTest$TwoBytes;
@ 32 --- instance fields end ---
@ 32 --- instance ends ---
@120 --- static fields start ---
@120 --- static fields end ---
The total size allocated for TestClass1's fields is 20 bytes, whereas the real payload is only 4 bytes.
This artificial alignment constraint also increased the size of the standalone inline class instances:
TestClass1: field layout
@ 12 --- instance fields start ---
@ 16 "tb0" QFlatteningTest$TwoBytes;
@ 24 "tb1" QFlatteningTest$TwoBytes;
@ 32 --- instance fields end ---
@ 32 --- instance ends ---
@120 --- static fields start ---
@120 --- static fields end ---
Instead of allocating fields tb0,tb1 just after the object header, the 64-bits alignment forces the layout to jump to the first 64-bits boundary (offset 16) before allocating them.
The prototyping code was largely inefficient, but there was no easy way to fix it. The field layouts are computed in the infamous ClassFileParser::layout_fields() method: a huge monolithic method with dozen of interdependent local variables. Huge re-work of this method would have been required to just introduce concepts of user-defined sizes and alignment constraint. And such changes would have make this method even bigger and even harder to read.
The proposed fix is to provide a framework to abstract layouts and elements of a layout. Then, use this framework to implement several ad hoc allocation strategies, with different goals and tradeoffs. Each allocation strategy would be much more easier to implement and to read.
The framework must be generic enough to support future layout enhancements (like support for hyper-alignment).
The abstraction for elements of a layout must be flexible enough to support both statically defined and user defined sizes and alignments.
Layout abstraction must be independent from the allocation strategies. Allocation strategies must be free to allocate fields in the order they consider best for their goals. It must be possible to implement multiple strategies instead of a single fit-all allocation code (for instance, inline classes and regular classes do not have the same goals, and should have distinct allocation strategies).
Meta-data in the JVM would also require some extensions, in addition to the new layout code, to store the new information required to compute layouts more efficiently (alignment constraints, exact sizes in bytes).
An implementation of this proposal can be found here:
http://cr.openjdk.java.net/~fparain/layout/webrev.00/
The code contains a lot of comments describing the framework, the elements of the framework, and the different allocation strategies.
With this implementation, the layout of the TestClass1 is now:
Layout of class TestClass1
|offset|kind|size|alignment|signature|name|
Instance fields:
0 RESERVED 12
12 FLATTENED 2 1 QTwoBytes; tb0
14 FLATTENED 2 1 QTwoBytes; tb1
Static fields
0 RESERVED 120
And the layout of the TwoBytes class:
Layout of class FlatteningTest$TwoBytes
|offset|kind|size|alignment|signature|name|
Instance fields:
0 RESERVED 12
12 REGULAR 1 1 B b0
13 REGULAR 1 1 B b1
Static fields
0 RESERVED 120
120 REGULAR 4 4 java/lang/Object .default
No more space wasted!
- duplicates
-
JDK-8237767 Field layout computation overhaul
- Resolved
- relates to
-
JDK-8228749 Deprecate product flag -XX:CompactFields
- Resolved
-
JDK-8228753 Deprecate -XX:FieldsAllocationStyle product option
- Resolved
-
JDK-8237767 Field layout computation overhaul
- Resolved
-
JDK-8236224 Obsolete the FieldsAllocationStyle and CompactFields options
- Resolved