diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 625d0a6a51..4c32e6258d 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -203,15 +203,24 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi final Map.Entry[] entityEntries = persistenceContext.reentrantSafeEntityEntries(); final int count = entityEntries.length; + FlushEntityEvent entityEvent = null; //allow reuse of the event as it's heavily allocated in certain use cases + int eventGenerationId = 0; //Used to double-check the instance reuse won't cause problems + for ( Map.Entry me : entityEntries ) { // Update the status of the object and if necessary, schedule an update EntityEntry entry = me.getValue(); Status status = entry.getStatus(); + if ( status != Status.LOADING && status != Status.GONE ) { - final FlushEntityEvent entityEvent = new FlushEntityEvent( source, me.getKey(), entry ); + entityEvent = createOrReuseEventInstance( entityEvent, source, me.getKey(), entry ); + + entityEvent.setInstanceGenerationId( ++eventGenerationId ); + flushListeners.fireEventOnEachListener( entityEvent, FlushEntityEventListener::onFlushEntity ); + entityEvent.setAllowedToReuse( true ); + assert entityEvent.getInstanceGenerationId() == eventGenerationId; } } @@ -220,6 +229,26 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi return count; } + /** + * Reuses a FlushEntityEvent for a new purpose, if possible; + * if not possible a new actual instance is returned. + */ + private FlushEntityEvent createOrReuseEventInstance( + FlushEntityEvent possiblyValidExistingInstance, + EventSource source, + Object key, + EntityEntry entry) { + FlushEntityEvent entityEvent = possiblyValidExistingInstance; + if ( entityEvent == null || (! entityEvent.isAllowedToReuse() ) ) { + //need to create a new instance + entityEvent = new FlushEntityEvent( source, key, entry ); + } + else { + entityEvent.resetAndReuseEventInstance( key, entry ); + } + return entityEvent; + } + /** * process any unreferenced collections and then inspect all known collections, * scheduling creates/removes/updates diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/FlushEntityEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/FlushEntityEvent.java index a3429717b3..d20e3c3718 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/FlushEntityEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/FlushEntityEvent.java @@ -12,15 +12,18 @@ import org.hibernate.engine.spi.EntityEntry; * @author Gavin King */ public class FlushEntityEvent extends AbstractEvent { - private final Object entity; + + private Object entity; private Object[] propertyValues; private Object[] databaseSnapshot; private int[] dirtyProperties; private boolean hasDirtyCollection; private boolean dirtyCheckPossible; private boolean dirtyCheckHandledByInterceptor; - private final EntityEntry entityEntry; - + private EntityEntry entityEntry; + private boolean allowedToReuse;//allows this event instance to be reused for multiple events: special case to GC + private int instanceGenerationId;//in support of event instance reuse: to double check no recursive/nested use is happening + public FlushEntityEvent(EventSource source, Object entity, EntityEntry entry) { super(source); this.entity = entity; @@ -75,4 +78,45 @@ public class FlushEntityEvent extends AbstractEvent { public Object getEntity() { return entity; } + + /** + * This is a terrible anti-pattern, but particular circumstances call for being + * able to reuse the same event instance: this is otherwise allocated in hot loops + * and since each event is escaping the scope it's actually causing allocation issues. + * The flush event does not appear to be used recursively so this is currently safe to + * do, nevertheless we add an allowedToReuse flag to ensure only instances whose + * purpose has completed are being reused. + * N.B. two out of three parameters from the constructor are reset: the same EventSource is implied + * on reuse. + * @param entity same as constructor parameter + * @param entry same as constructor parameter + */ + public void resetAndReuseEventInstance(Object entity, EntityEntry entry) { + this.entity = entity; + this.entityEntry = entry; + this.allowedToReuse = false; + //and reset other fields to the default: + this.propertyValues = null; + this.databaseSnapshot = null; + this.dirtyProperties = null; + this.hasDirtyCollection = false; + this.dirtyCheckPossible = false; + this.dirtyCheckHandledByInterceptor = false; + } + + public boolean isAllowedToReuse() { + return this.allowedToReuse; + } + + public void setAllowedToReuse(final boolean allowedToReuse) { + this.allowedToReuse = allowedToReuse; + } + + public int getInstanceGenerationId() { + return this.instanceGenerationId; + } + + public void setInstanceGenerationId(final int instanceGenerationId) { + this.instanceGenerationId = instanceGenerationId; + } }