This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 7fe25cd54e90f54c4514dbb52e9bac3a4f6235b0 Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Mon Mar 10 16:04:16 2025 +0400 CAY-2876 Memory leak in the ObjectStore --- RELEASE-NOTES.txt | 1 + .../org/apache/cayenne/access/ObjectStore.java | 34 ++++++++++++++++++---- .../java/org/apache/cayenne/util/ReferenceMap.java | 20 +++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f964462b3..52ab66e33 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -21,6 +21,7 @@ Bug Fixes: CAY-2701 MySQL DST-related LocalDateTime issues CAY-2871 QualifierTranslator breaks on a relationship with a compound FK CAY-2872 CayenneModeler "Documentation" link is broken +CAY-2876 Memory leak in the ObjectStore CAY-2879 Negative number for non parameterized ObjectSelect query not processed correctly ---------------------------------- diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java index 0dcdc7aa1..8800fd5e8 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java @@ -44,6 +44,8 @@ import org.apache.cayenne.reflect.ClassDescriptor; import org.apache.cayenne.reflect.PropertyVisitor; import org.apache.cayenne.reflect.ToManyProperty; import org.apache.cayenne.reflect.ToOneProperty; +import org.apache.cayenne.util.SoftValueMap; +import org.apache.cayenne.util.WeakValueMap; import java.io.Serializable; import java.util.ArrayList; @@ -108,13 +110,23 @@ public class ObjectStore implements Serializable, SnapshotEventListener, GraphMa */ public ObjectStore(DataRowStore dataRowCache, Map<Object, Persistent> objectMap) { setDataRowCache(dataRowCache); - if (objectMap != null) { - this.objectMap = objectMap; - } - else { + setObjectMap(objectMap); + this.changes = new HashMap<>(); + } + + /** + * @since 4.2.2 + */ + void setObjectMap(Map<Object, Persistent> objectMap) { + if(objectMap == null) { throw new CayenneRuntimeException("Object map is null."); } - this.changes = new HashMap<>(); + this.objectMap = objectMap; + if(objectMap instanceof SoftValueMap) { + ((SoftValueMap<Object, Persistent>) objectMap).setKeyCleanupCallback(this::onObjectKeyCleanup); + } else if(objectMap instanceof WeakValueMap) { + ((WeakValueMap<Object, Persistent>) objectMap).setKeyCleanupCallback(this::onObjectKeyCleanup); + } } /** @@ -137,7 +149,7 @@ public class ObjectStore implements Serializable, SnapshotEventListener, GraphMa Collection<GraphDiff> getLifecycleEventInducedChanges() { return lifecycleEventInducedChanges != null ? lifecycleEventInducedChanges - : Collections.<GraphDiff>emptyList(); + : Collections.emptyList(); } void registerLifecycleEventInducedChange(GraphDiff diff) { @@ -1036,6 +1048,16 @@ public class ObjectStore implements Serializable, SnapshotEventListener, GraphMa .put(path, id); } + /** + * @param key object id that was removed from the {@link #objectMap} + * @since 4.2.2 + */ + void onObjectKeyCleanup(Object key) { + if(trackedFlattenedPaths != null) { + trackedFlattenedPaths.remove(key); + } + } + // an ObjectIdQuery optimized for retrieval of multiple snapshots - it can be reset // with the new id static final class CachedSnapshotQuery extends ObjectIdQuery { diff --git a/cayenne/src/main/java/org/apache/cayenne/util/ReferenceMap.java b/cayenne/src/main/java/org/apache/cayenne/util/ReferenceMap.java index 7eb814923..15daf5352 100644 --- a/cayenne/src/main/java/org/apache/cayenne/util/ReferenceMap.java +++ b/cayenne/src/main/java/org/apache/cayenne/util/ReferenceMap.java @@ -35,6 +35,7 @@ import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.function.Consumer; /** * Map that transparently stores values as references and resolves them as needed. @@ -82,6 +83,11 @@ abstract class ReferenceMap<K, V, R extends Reference<V>> extends AbstractMap<K, */ protected transient Set<Entry<K, V>> entrySet; + /** + * @since 4.2.2 + */ + protected transient Consumer<K> keyCleanupCallback; + public ReferenceMap() { map = new HashMap<>(); referenceQueue = new ReferenceQueue<>(); @@ -221,6 +227,17 @@ abstract class ReferenceMap<K, V, R extends Reference<V>> extends AbstractMap<K, return es; } + /** + * Set callback that will be notified with a key on each value removal + * due to the corresponding value reference cleaned up by the GC. + * + * @param keyCleanupCallback callback to set + * @since 4.2.2 + */ + public void setKeyCleanupCallback(Consumer<K> keyCleanupCallback) { + this.keyCleanupCallback = keyCleanupCallback; + } + /** * Cleanup all references collected by GC so far */ @@ -247,6 +264,9 @@ abstract class ReferenceMap<K, V, R extends Reference<V>> extends AbstractMap<K, for(K keyToRemove : keysToRemove) { map.remove(keyToRemove); + if(keyCleanupCallback != null) { + keyCleanupCallback.accept(keyToRemove); + } } }