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());
+    }
 }

Reply via email to