This is an automated email from the ASF dual-hosted git repository.
zhangzicheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 4491cb8eb [type:fix] fix MemorySafeWindowTinyLFUMap memory leak error
(#4524)
4491cb8eb is described below
commit 4491cb8eb2778614d5fe5d9d14a278d60393a3b1
Author: xuyicheng1995 <[email protected]>
AuthorDate: Sun Apr 2 15:51:49 2023 +0800
[type:fix] fix MemorySafeWindowTinyLFUMap memory leak error (#4524)
* [type:fix] fix MemorySafeWindowTinyLFUMap memory leak error
---
.../common/cache/MemorySafeWindowTinyLFUMap.java | 66 +++++++++++++---------
.../cache/MemorySafeWindowTinyLFUMapTest.java | 24 +++++++-
2 files changed, 61 insertions(+), 29 deletions(-)
diff --git
a/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMap.java
b/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMap.java
index 6c382f768..44fd73287 100644
---
a/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMap.java
+++
b/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMap.java
@@ -26,12 +26,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import javax.annotation.concurrent.ThreadSafe;
import java.io.Serializable;
+import java.lang.ref.WeakReference;
import java.util.AbstractMap;
-import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -44,22 +45,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
@ThreadSafe
public class MemorySafeWindowTinyLFUMap<K, V> extends AbstractMap<K, V>
implements Serializable {
-
+
private static final long serialVersionUID = -3288161459386389022L;
-
+
private static final AtomicBoolean GLOBAL = new AtomicBoolean(false);
-
- private static final Set<MemorySafeWindowTinyLFUMap<?, ?>> ALL = new
LinkedHashSet<>();
-
+
+ private static final Set<WeakReference<MemorySafeWindowTinyLFUMap<?, ?>>>
ALL = new CopyOnWriteArraySet<>();
+
private final int maxFreeMemory;
-
+
private final Cache<K, V> cache;
-
+
public MemorySafeWindowTinyLFUMap(final int maxFreeMemory,
final int initialSize) {
this(maxFreeMemory, initialSize, Long.MAX_VALUE,
Constants.LRU_MAP_MAXSIZE);
}
-
+
public MemorySafeWindowTinyLFUMap(final int maxFreeMemory,
final int initialSize,
final long expireAfterWrite,
@@ -72,12 +73,12 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
.initialCapacity(initialSize)
.build();
}
-
+
@Override
public V get(final Object key) {
return cache.getIfPresent(key);
}
-
+
@Override
public V put(final K key, final V value) {
checkAndScheduleRefresh(this);
@@ -85,7 +86,7 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
cache.put(key, value);
return previous;
}
-
+
@Override
public V remove(final Object key) {
final V previous = cache.getIfPresent(key);
@@ -93,12 +94,12 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
cache.cleanUp();
return previous;
}
-
+
@Override
public Set<Entry<K, V>> entrySet() {
return cache.asMap().entrySet();
}
-
+
/**
* clean invalidated cache now.
*/
@@ -107,18 +108,21 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
invalidate();
}
}
-
+
/**
* invalidate coldest cache now.
*/
public void invalidate() {
cache.policy().eviction().ifPresent(eviction -> {
final Map<@NonNull K, @NonNull V> coldest = eviction.coldest(1);
+ if (coldest.size() == 0) {
+ return;
+ }
Optional.ofNullable(coldest.entrySet().iterator().next())
- .ifPresent(entry -> remove(entry.getKey()));
+ .ifPresent(entry -> cache.invalidate(entry.getKey()));
});
}
-
+
/**
* whether to full.
*
@@ -128,7 +132,7 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
// when free memory less than certain value, consider it's full
return cache.estimatedSize() > 0 &&
MemoryLimitCalculator.maxAvailable() < maxFreeMemory;
}
-
+
@Override
public boolean equals(final Object o) {
if (this == o) {
@@ -143,14 +147,14 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
MemorySafeWindowTinyLFUMap<?, ?> that = (MemorySafeWindowTinyLFUMap<?,
?>) o;
return maxFreeMemory == that.maxFreeMemory && Objects.equals(cache,
that.cache);
}
-
+
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), maxFreeMemory, cache);
}
-
+
private static void checkAndScheduleRefresh(final
MemorySafeWindowTinyLFUMap<?, ?> map) {
- ALL.add(map);
+ ALL.add(new WeakReference<>(map));
if (!GLOBAL.get()) {
refresh();
if (GLOBAL.compareAndSet(false, true)) {
@@ -165,14 +169,22 @@ public class MemorySafeWindowTinyLFUMap<K, V> extends
AbstractMap<K, V> implemen
}
}
}
-
+
private static void refresh() {
- boolean anyFull =
ALL.stream().anyMatch(MemorySafeWindowTinyLFUMap::isFull);
- while (anyFull) {
- for (MemorySafeWindowTinyLFUMap<?, ?> map : ALL) {
- map.invalidate();
+ // try to clear weak reference
+ for (WeakReference<MemorySafeWindowTinyLFUMap<?, ?>> weakReference :
ALL) {
+ MemorySafeWindowTinyLFUMap<?, ?> cacheMap = weakReference.get();
+ if (cacheMap == null) {
+ ALL.remove(weakReference);
}
- anyFull =
ALL.stream().anyMatch(MemorySafeWindowTinyLFUMap::isFull);
+ }
+ // if jvm memory is full, try to release memory from caffine
+ boolean anyFull =
ALL.stream().map(WeakReference::get).filter(Objects::nonNull)
+ .anyMatch(MemorySafeWindowTinyLFUMap::isFull);
+ while (anyFull) {
+
ALL.stream().map(WeakReference::get).filter(Objects::nonNull).forEach(MemorySafeWindowTinyLFUMap::invalidate);
+ anyFull =
ALL.stream().map(WeakReference::get).filter(Objects::nonNull)
+ .anyMatch(MemorySafeWindowTinyLFUMap::isFull);
}
}
}
diff --git
a/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMapTest.java
b/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMapTest.java
index 38f20a2f6..b0ac08a19 100644
---
a/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMapTest.java
+++
b/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeWindowTinyLFUMapTest.java
@@ -17,16 +17,19 @@
package org.apache.shenyu.common.cache;
+import org.apache.shenyu.common.utils.ReflectUtils;
import org.junit.Assert;
import org.junit.Test;
+import java.lang.ref.WeakReference;
import java.util.Map;
+import java.util.Set;
/**
* Test cases for MemorySafeWindowTinyLFUMap.
*/
public class MemorySafeWindowTinyLFUMapTest {
-
+
@Test
public void testPut() {
MemorySafeWindowTinyLFUMap<String, String> lru = new
MemorySafeWindowTinyLFUMap<>(1 << 10, 16);
@@ -44,10 +47,15 @@ public class MemorySafeWindowTinyLFUMapTest {
private static final long serialVersionUID = 8897028073615563875L;
@Override
- public boolean isFull() {
+ public synchronized boolean isFull() {
//just for test
return size() > 1;
}
+
+ @Override
+ public synchronized void cleanUp() {
+ super.cleanUp();
+ }
};
cache.put(1, 1);
Assert.assertEquals(1, cache.size());
@@ -62,4 +70,16 @@ public class MemorySafeWindowTinyLFUMapTest {
Assert.assertEquals(3, (int) key);
Assert.assertEquals(3, (int) value);
}
+
+ @Test
+ public void testWindowTinyLFUOutOufMemoryException() {
+ final int mb = 1 * 1024 * 1024;
+ for (int i = 0; i < 1000; i++) {
+ MemorySafeWindowTinyLFUMap<String, Byte[]> instance = new
MemorySafeWindowTinyLFUMap<>(1, 1024);
+ instance.put(String.valueOf(1), new Byte[mb]);
+ }
+ Set<WeakReference<MemorySafeWindowTinyLFUMap<?, ?>>> all =
+ (Set<WeakReference<MemorySafeWindowTinyLFUMap<?, ?>>>)
ReflectUtils.getFieldValue(new MemorySafeWindowTinyLFUMap(1, 1024), "ALL");
+ Assert.assertNotEquals(1000, all.size());
+ }
}