fix(memory): ReflectionUtil 添加 SoftReference + TTL 缓存清理

原代码使用永久缓存 Reflections 实例,占用大量内存且不释放。

改为:
- 使用 SoftReference 允许 GC 在内存不足时回收
- 添加 1 小时 TTL 防止长期占用
- 每次获取时自动清理过期条目
This commit is contained in:
yukaidi
2026-05-29 00:32:02 +08:00
parent 1fca578c07
commit be1ed3d46d

View File

@@ -18,12 +18,14 @@ import org.reflections.util.FilterBuilder;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
@@ -37,7 +39,26 @@ import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
public final class ReflectionUtil {
// 缓存Reflections实例避免重复扫描每次扫描约35K+值耗时1-3秒占用大量内存
private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
// 使用 SoftReference 允许 GC 在内存不足时回收,同时添加 TTL 防止长期占用
private static final Map<String, SoftReference<Reflections>> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
private static final long CACHE_TTL_MS = TimeUnit.HOURS.toMillis(1); // 1小时 TTL
private static final Map<String, Long> CACHE_TIMESTAMP = new java.util.concurrent.ConcurrentHashMap<>();
/**
* 清理过期的缓存条目
*/
private static void cleanExpiredCache() {
long now = System.currentTimeMillis();
Iterator<Map.Entry<String, Long>> iterator = CACHE_TIMESTAMP.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Long> entry = iterator.next();
if (now - entry.getValue() > CACHE_TTL_MS) {
String key = entry.getKey();
REFLECTIONS_CACHE.remove(key);
iterator.remove();
}
}
}
/**
* 以默认配置的基础包路径获取反射器
@@ -49,34 +70,50 @@ public final class ReflectionUtil {
}
/**
* 获取反射器(带缓存)
* 获取反射器(带缓存,支持 TTL 和 SoftReference
*
* @param packageAddress Package address String
* @return Reflections object
*/
public static Reflections getReflections(String packageAddress) {
return REFLECTIONS_CACHE.computeIfAbsent(packageAddress, key -> {
List<String> packageAddressList;
if (key.contains(",")) {
packageAddressList = Arrays.asList(key.split(","));
} else if (key.contains(";")) {
packageAddressList = Arrays.asList(key.split(";"));
} else {
packageAddressList = Collections.singletonList(key);
}
return createReflections(packageAddressList);
});
cleanExpiredCache();
SoftReference<Reflections> ref = REFLECTIONS_CACHE.get(packageAddress);
Reflections reflections = ref != null ? ref.get() : null;
if (reflections != null) {
return reflections;
}
List<String> packageAddressList;
if (packageAddress.contains(",")) {
packageAddressList = Arrays.asList(packageAddress.split(","));
} else if (packageAddress.contains(";")) {
packageAddressList = Arrays.asList(packageAddress.split(";"));
} else {
packageAddressList = Collections.singletonList(packageAddress);
}
reflections = createReflections(packageAddressList);
REFLECTIONS_CACHE.put(packageAddress, new SoftReference<>(reflections));
CACHE_TIMESTAMP.put(packageAddress, System.currentTimeMillis());
return reflections;
}
/**
* 获取反射器(带缓存)
* 获取反射器(带缓存,支持 TTL 和 SoftReference
*
* @param packageAddresses Package address List
* @return Reflections object
*/
public static Reflections getReflections(List<String> packageAddresses) {
cleanExpiredCache();
String cacheKey = String.join(",", packageAddresses);
return REFLECTIONS_CACHE.computeIfAbsent(cacheKey, key -> createReflections(packageAddresses));
SoftReference<Reflections> ref = REFLECTIONS_CACHE.get(cacheKey);
Reflections reflections = ref != null ? ref.get() : null;
if (reflections != null) {
return reflections;
}
reflections = createReflections(packageAddresses);
REFLECTIONS_CACHE.put(cacheKey, new SoftReference<>(reflections));
CACHE_TIMESTAMP.put(cacheKey, System.currentTimeMillis());
return reflections;
}
private static Reflections createReflections(List<String> packageAddresses) {