You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2025/03/11 12:41:00 UTC
(cayenne) 01/02: CAY-2876 Memory leak in the ObjectStore
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 <st...@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);
+ }
}
}