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.annotation.Annotation;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS; 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 { public final class ReflectionUtil {
// 缓存Reflections实例避免重复扫描每次扫描约35K+值耗时1-3秒占用大量内存 // 缓存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 * @param packageAddress Package address String
* @return Reflections object * @return Reflections object
*/ */
public static Reflections getReflections(String packageAddress) { public static Reflections getReflections(String packageAddress) {
return REFLECTIONS_CACHE.computeIfAbsent(packageAddress, key -> { cleanExpiredCache();
List<String> packageAddressList; SoftReference<Reflections> ref = REFLECTIONS_CACHE.get(packageAddress);
if (key.contains(",")) { Reflections reflections = ref != null ? ref.get() : null;
packageAddressList = Arrays.asList(key.split(",")); if (reflections != null) {
} else if (key.contains(";")) { return reflections;
packageAddressList = Arrays.asList(key.split(";"));
} else {
packageAddressList = Collections.singletonList(key);
} }
return createReflections(packageAddressList); 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 * @param packageAddresses Package address List
* @return Reflections object * @return Reflections object
*/ */
public static Reflections getReflections(List<String> packageAddresses) { public static Reflections getReflections(List<String> packageAddresses) {
cleanExpiredCache();
String cacheKey = String.join(",", packageAddresses); 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) { private static Reflections createReflections(List<String> packageAddresses) {