To reduce memory footprint, the JFR parser creates a field of type ObjectContext in each RecordedObject where it can access time conversion utilities, so timestamps can be stored as long values (ticks) in an event instead of Instant and Duration objects. With value types we may want reconsider this, but works well for now.
The ObjectContext also contains the event type and the list of fields of the RecordedObject to reduce the number of fields needed in each RecordedObject and RecordedEvent
Thera are a few problems with the implementation:
- As substructures a traversed, for example getValue("object.referrer.object.referrer..."), it creates a new ObjectContext for each new ValueDescriptor. This is better than one per traversed object, but still unnecessary many. For example, if there are two fields of type RecordedClass in an event, there only needs to be one mapping from ValueDescriptor to its subfields.
(Preferable would be one per recording, but per event type may be easier to bookkeep. Once an event type is unregisteded, the associated cache will be garbage collected.)
- The cache uses a HashMap<ValueDescriptor, ObjectContext>, but it would more efficient to use an IdentityHashMap, fewer CPU cycles and less memory footprint. IdentityHashMap also signals intent better.
- If multiple threads access the same RecordedObject and builds the cache simultaneously, there could be a ConcurrentModificationException
A better approach is to traverse all reachable ValueDescriptors at first access and store the result in a prebuilt cache used by all ObjectContexts for that event type. In the unlikely case two threads access the same RecordedObject, they would build their own version.
class ObjectContext {
IdentityHashMap<ValueDescriptor, ObjectContext> contexts;
TimeConverter timeConverter;
EventType eventType;
List<ValueDescriptor> fields;
public ObjectContext getInstance(ValueDescriptor descriptor) {
if (contexts == null) {
context = buildAllReachableContexts(eventType);
}
return contexts.get(descriptor);
}
}
The ObjectContext also contains the event type and the list of fields of the RecordedObject to reduce the number of fields needed in each RecordedObject and RecordedEvent
Thera are a few problems with the implementation:
- As substructures a traversed, for example getValue("object.referrer.object.referrer..."), it creates a new ObjectContext for each new ValueDescriptor. This is better than one per traversed object, but still unnecessary many. For example, if there are two fields of type RecordedClass in an event, there only needs to be one mapping from ValueDescriptor to its subfields.
(Preferable would be one per recording, but per event type may be easier to bookkeep. Once an event type is unregisteded, the associated cache will be garbage collected.)
- The cache uses a HashMap<ValueDescriptor, ObjectContext>, but it would more efficient to use an IdentityHashMap, fewer CPU cycles and less memory footprint. IdentityHashMap also signals intent better.
- If multiple threads access the same RecordedObject and builds the cache simultaneously, there could be a ConcurrentModificationException
A better approach is to traverse all reachable ValueDescriptors at first access and store the result in a prebuilt cache used by all ObjectContexts for that event type. In the unlikely case two threads access the same RecordedObject, they would build their own version.
class ObjectContext {
IdentityHashMap<ValueDescriptor, ObjectContext> contexts;
TimeConverter timeConverter;
EventType eventType;
List<ValueDescriptor> fields;
public ObjectContext getInstance(ValueDescriptor descriptor) {
if (contexts == null) {
context = buildAllReachableContexts(eventType);
}
return contexts.get(descriptor);
}
}