From af977ac3d4cd44fe576e4f0ab462c4bbf439e825 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 10 Apr 2022 18:30:07 +0800 Subject: [PATCH] fix weak bug --- .../cn/hutool/cache/impl/AbstractCache.java | 32 ++++- .../java/cn/hutool/cache/impl/FIFOCache.java | 4 +- .../java/cn/hutool/cache/impl/LFUCache.java | 4 +- .../java/cn/hutool/cache/impl/LRUCache.java | 2 +- .../cn/hutool/cache/impl/ReentrantCache.java | 6 +- .../cn/hutool/cache/impl/StampedCache.java | 8 +- .../java/cn/hutool/cache/impl/TimedCache.java | 5 +- .../java/cn/hutool/cache/impl/WeakCache.java | 110 ++---------------- .../java/cn/hutool/cache/WeakCacheTest.java | 24 ++++ .../hutool/core/lang/mutable/MutableObj.java | 11 ++ .../hutool/core/util/ReferenceUtilTest.java | 22 ++++ 11 files changed, 106 insertions(+), 122 deletions(-) diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index 6bb549689..a52ac4713 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -3,6 +3,8 @@ package cn.hutool.cache.impl; import cn.hutool.cache.Cache; import cn.hutool.cache.CacheListener; import cn.hutool.core.lang.func.Func0; +import cn.hutool.core.lang.mutable.Mutable; +import cn.hutool.core.lang.mutable.MutableObj; import java.util.Iterator; import java.util.Map; @@ -11,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; /** * 超时和限制大小的缓存的默认实现
@@ -27,7 +30,7 @@ import java.util.concurrent.locks.ReentrantLock; public abstract class AbstractCache implements Cache { private static final long serialVersionUID = 1L; - protected Map> cacheMap; + protected Map, CacheObj> cacheMap; /** * 写的时候每个key一把锁,降低锁的粒度 @@ -84,7 +87,7 @@ public abstract class AbstractCache implements Cache { if (isFull()) { pruneCache(); } - cacheMap.put(key, co); + cacheMap.put(MutableObj.of(key), co); } // ---------------------------------------------------------------- put end @@ -112,7 +115,7 @@ public abstract class AbstractCache implements Cache { keyLock.lock(); try { // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入 - final CacheObj co = cacheMap.get(key); + final CacheObj co = getWithoutLock(key); if (null == co || co.isExpired()) { try { v = supplier.call(); @@ -130,6 +133,16 @@ public abstract class AbstractCache implements Cache { } return v; } + + /** + * 获取键对应的{@link CacheObj} + * @param key 键,实际使用时会被包装为{@link MutableObj} + * @return {@link CacheObj} + * @since 5.8.0 + */ + protected CacheObj getWithoutLock(K key){ + return this.cacheMap.get(MutableObj.of(key)); + } // ---------------------------------------------------------------- get end @Override @@ -212,7 +225,7 @@ public abstract class AbstractCache implements Cache { * @since 5.5.9 */ public Set keySet(){ - return this.cacheMap.keySet(); + return this.cacheMap.keySet().stream().map(Mutable::get).collect(Collectors.toSet()); } /** @@ -237,11 +250,20 @@ public abstract class AbstractCache implements Cache { * @return 移除的对象,无返回null */ protected CacheObj removeWithoutLock(K key, boolean withMissCount) { - final CacheObj co = cacheMap.remove(key); + final CacheObj co = cacheMap.remove(MutableObj.of(key)); if (withMissCount) { // 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1 this.missCount.increment(); } return co; } + + /** + * 获取所有{@link CacheObj}值的{@link Iterator}形式 + * @return {@link Iterator} + * @since 5.8.0 + */ + protected Iterator> cacheObjIter(){ + return this.cacheMap.values().iterator(); + } } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java index 558fed75f..a3fc35085 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java @@ -50,7 +50,7 @@ public class FIFOCache extends StampedCache { CacheObj first = null; // 清理过期对象并找出链表头部元素(先入元素) - Iterator> values = cacheMap.values().iterator(); + final Iterator> values = cacheObjIter(); if (isPruneExpiredActive()) { // 清理过期对象并找出链表头部元素(先入元素) while (values.hasNext()) { @@ -71,7 +71,7 @@ public class FIFOCache extends StampedCache { // 清理结束后依旧是满的,则删除第一个被缓存的对象 if (isFull() && null != first) { - cacheMap.remove(first.key); + removeWithoutLock(first.key, false); onRemove(first.key, first.obj); count++; } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java index 4b1e2608a..b3bcdffc9 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java @@ -57,7 +57,7 @@ public class LFUCache extends StampedCache { CacheObj comin = null; // 清理过期对象并找出访问最少的对象 - Iterator> values = cacheMap.values().iterator(); + Iterator> values = cacheObjIter(); CacheObj co; while (values.hasNext()) { co = values.next(); @@ -78,7 +78,7 @@ public class LFUCache extends StampedCache { if (isFull() && comin != null) { long minAccessCount = comin.accessCount.get(); - values = cacheMap.values().iterator(); + values = cacheObjIter(); CacheObj co1; while (values.hasNext()) { co1 = values.next(); diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java index 29f83f02f..c95b97f2e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java @@ -56,7 +56,7 @@ public class LRUCache extends ReentrantCache { return 0; } int count = 0; - Iterator> values = cacheMap.values().iterator(); + Iterator> values = cacheObjIter(); CacheObj co; while (values.hasNext()) { co = values.next(); diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java index a985ad70b..e45474040 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java @@ -36,7 +36,7 @@ public abstract class ReentrantCache extends AbstractCache { lock.lock(); try { // 不存在或已移除 - final CacheObj co = cacheMap.get(key); + final CacheObj co = getWithoutLock(key); if (co == null) { return false; } @@ -59,7 +59,7 @@ public abstract class ReentrantCache extends AbstractCache { CacheObj co; lock.lock(); try { - co = cacheMap.get(key); + co = getWithoutLock(key); } finally { lock.unlock(); } @@ -83,7 +83,7 @@ public abstract class ReentrantCache extends AbstractCache { CopiedIter> copiedIterator; lock.lock(); try { - copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); + copiedIterator = CopiedIter.copyOf(cacheObjIter()); } finally { lock.unlock(); } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java index 79534f2bf..deaec6641 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java @@ -36,7 +36,7 @@ public abstract class StampedCache extends AbstractCache{ final long stamp = lock.readLock(); try { // 不存在或已移除 - final CacheObj co = cacheMap.get(key); + final CacheObj co = getWithoutLock(key); if (co == null) { return false; } @@ -58,12 +58,12 @@ public abstract class StampedCache extends AbstractCache{ public V get(K key, boolean isUpdateLastAccess) { // 尝试读取缓存,使用乐观读锁 long stamp = lock.tryOptimisticRead(); - CacheObj co = cacheMap.get(key); + CacheObj co = getWithoutLock(key); if(false == lock.validate(stamp)){ // 有写线程修改了此对象,悲观读 stamp = lock.readLock(); try { - co = cacheMap.get(key); + co = getWithoutLock(key); } finally { lock.unlockRead(stamp); } @@ -88,7 +88,7 @@ public abstract class StampedCache extends AbstractCache{ CopiedIter> copiedIterator; final long stamp = lock.readLock(); try { - copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); + copiedIterator = CopiedIter.copyOf(cacheObjIter()); } finally { lock.unlockRead(stamp); } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java index e03ede728..dfccd942e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java @@ -1,6 +1,7 @@ package cn.hutool.cache.impl; import cn.hutool.cache.GlobalPruneTimer; +import cn.hutool.core.lang.mutable.Mutable; import java.util.HashMap; import java.util.Iterator; @@ -37,7 +38,7 @@ public class TimedCache extends StampedCache { * @param timeout 过期时长 * @param map 存储缓存对象的map */ - public TimedCache(long timeout, Map> map) { + public TimedCache(long timeout, Map, CacheObj> map) { this.capacity = 0; this.timeout = timeout; this.cacheMap = map; @@ -52,7 +53,7 @@ public class TimedCache extends StampedCache { @Override protected int pruneCache() { int count = 0; - Iterator> values = cacheMap.values().iterator(); + final Iterator> values = cacheObjIter(); CacheObj co; while (values.hasNext()) { co = values.next(); diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java index 21b2bb16a..90df20303 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java @@ -1,10 +1,5 @@ package cn.hutool.cache.impl; -import cn.hutool.cache.Cache; -import cn.hutool.core.lang.func.Func0; -import cn.hutool.core.lang.mutable.MutableObj; - -import java.util.Iterator; import java.util.WeakHashMap; /** @@ -12,112 +7,21 @@ import java.util.WeakHashMap; * 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。
* 丢弃某个键时,其条目从映射中有效地移除。
* - * @param 键类型 - * @param 值类型 * @author Looly + * + * @param 键 + * @param 值 + * @author looly * @since 3.0.7 */ -public class WeakCache implements Cache { +public class WeakCache extends TimedCache{ private static final long serialVersionUID = 1L; - TimedCache, V> timedCache; - /** * 构造 - * - * @param timeout 超时 + * @param timeout 超时时常,单位毫秒,-1或0表示无限制 */ public WeakCache(long timeout) { - this.timedCache = new TimedCache<>(timeout, new WeakHashMap<>()); - } - - @Override - public int capacity() { - return timedCache.capacity(); - } - - @Override - public long timeout() { - return timedCache.timeout(); - } - - @Override - public void put(K key, V object) { - timedCache.put(new MutableObj<>(key), object); - } - - @Override - public void put(K key, V object, long timeout) { - timedCache.put(new MutableObj<>(key), object, timeout); - } - - @Override - public V get(K key, boolean isUpdateLastAccess, Func0 supplier) { - return timedCache.get(new MutableObj<>(key), isUpdateLastAccess, supplier); - } - - @Override - public V get(K key, boolean isUpdateLastAccess) { - return timedCache.get(new MutableObj<>(key), isUpdateLastAccess); - } - - @Override - public Iterator> cacheObjIterator() { - final Iterator, V>> timedIter = timedCache.cacheObjIterator(); - return new Iterator>() { - @Override - public boolean hasNext() { - return timedIter.hasNext(); - } - - @Override - public CacheObj next() { - final CacheObj, V> next = timedIter.next(); - final CacheObj nextNew = new CacheObj<>(next.key.get(), next.obj, next.ttl); - nextNew.lastAccess = next.lastAccess; - nextNew.accessCount = next.accessCount; - return nextNew; - } - }; - } - - @Override - public int prune() { - return timedCache.prune(); - } - - @Override - public boolean isFull() { - return timedCache.isFull(); - } - - @Override - public void remove(K key) { - timedCache.remove(new MutableObj<>(key)); - } - - @Override - public void clear() { - timedCache.clear(); - } - - @Override - public int size() { - return timedCache.size(); - } - - @Override - public boolean isEmpty() { - return timedCache.isEmpty(); - } - - @Override - public boolean containsKey(K key) { - return timedCache.containsKey(new MutableObj<>(key)); - } - - @Override - public Iterator iterator() { - return timedCache.iterator(); + super(timeout, new WeakHashMap<>()); } } diff --git a/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java index ce7b03dd8..19eb4f096 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java @@ -1,6 +1,7 @@ package cn.hutool.cache; import cn.hutool.cache.impl.WeakCache; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; @@ -14,8 +15,31 @@ public class WeakCacheTest { Assert.assertEquals(2, cache.size()); + // 检查被MutableObj包装的key能否正常移除 cache.remove("abc"); Assert.assertEquals(1, cache.size()); } + + @Test + public void removeByGcTest(){ + WeakCache cache = new WeakCache<>(-1); + cache.put("a", "1"); + cache.put("b", "2"); + Assert.assertEquals(2, cache.size()); + + // GC测试 + int i=0; + while(true){ + if(2 == cache.size()){ + i++; + Console.log("Object is alive for {} loops - ", i); + System.gc(); + }else{ + Console.log("Object has been collected."); + Console.log(cache.size()); + break; + } + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java index 72a490366..4b772c26e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java @@ -11,6 +11,17 @@ import java.io.Serializable; public class MutableObj implements Mutable, Serializable { private static final long serialVersionUID = 1L; + /** + * 构建MutableObj + * @param value 被包装的值 + * @param 值类型 + * @return MutableObj + * @since 5.8.0 + */ + public static MutableObj of(T value){ + return new MutableObj<>(value); + } + private T value; /** diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReferenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReferenceUtilTest.java index 97fa55953..c1aed085d 100755 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReferenceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReferenceUtilTest.java @@ -1,6 +1,9 @@ package cn.hutool.core.util; +import cn.hutool.core.lang.Console; +import cn.hutool.core.lang.mutable.MutableObj; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.ref.PhantomReference; @@ -31,4 +34,23 @@ public class ReferenceUtilTest { // get方法永远都返回null,PhantomReference只能用来监控对象的GC状况 Assert.assertNull(integerReference.get()); } + + @Test + @Ignore + public void gcTest(){ + // https://blog.csdn.net/zmx729618/article/details/54093532 + // 弱引用的对象必须使用可变对象,不能使用常量对象(比如String) + WeakReference> reference = new WeakReference<>(new MutableObj<>("abc")); + int i=0; + while(true){ + if(reference.get()!=null){ + i++; + Console.log("Object is alive for {} loops - ", i); + System.gc(); + }else{ + Console.log("Object has been collected."); + break; + } + } + } }