情况:
ThreadLocal
)的强引用的缓存(请参阅下面的编辑)。因此,要使用ThreadLocal
,它只能将WeakReference
保存到资源中,并且必须保留分离的强引用集合。代码很快变得非常复杂并且会产生内存泄漏(因为线程死亡后永远不会删除强引用)。
ConcurrentHashMap
似乎是一个很好的诉讼,但它也会受到内存泄漏的影响。
还有其他选择吗?一个同步的WeakHashMap ??
(希望解决方案也可以使用给定的Supplier
自动初始化,就像ThreadLocal.withInitial()
)
编辑:
只是为了证明类加载器泄漏是件事。使用以下命令创建一个最小的WAR项目:
public class Test {
public static ThreadLocal<Test> test = ThreadLocal.withInitial(Test::new);
}
的index.jsp:
<%= Test.test.get() %>
访问该页面并关闭Tomcat,您将获得:
Aug 21, 2015 5:56:11 PM org.apache.catalina.loader.WebappClassLoaderBase checkThreadLocalMapForLeaks
SEVERE: The web application [test] created a ThreadLocal with key of type [java.lang.ThreadLocal.SuppliedThreadLocal] (value [java.lang.ThreadLocal$SuppliedThreadLocal@54e69987]) and a value of type [test.Test] (value [test.Test@2a98020a]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
答案 0 :(得分:1)
这似乎是典型的“弱键,强大的价值引用关键”问题。如果您将值设置为弱,即使密钥可以访问也可以收集它,如果您将密钥设置为强,则密钥也可以很强地访问。如果没有JVM的直接支持,这是无法解决的。
值得庆幸的是,有一个类提供了这一点(尽管文档中没有强调它):
懒洋洋地将计算值与(可能)每种类型相关联。例如,如果动态语言需要为消息发送调用站点遇到的每个类构造一个消息调度表,它可以使用ClassValue来缓存为每个遇到的类快速执行消息发送所需的信息。
虽然本文档没有说这些值可能引用Class
键,但是为类保留调度表的预期用例意味着通常使用带有反向引用的值。
让我们用一个小测试类来演示它:
public class ClassValueTest extends ClassValue<Method> {
@Override
protected Method computeValue(Class<?> type) {
System.out.println("computeValue");
return Arrays.stream(type.getDeclaredMethods())
.filter(m->Modifier.isPublic(m.getModifiers()))
.findFirst().orElse(null);
}
public static void main(String... arg) throws Throwable {
// create a collectible class:
MethodHandles.Lookup l=MethodHandles.lookup();
MethodType noArg = MethodType.methodType(void.class);
MethodHandle println = l.findVirtual(
PrintStream.class, "println", MethodType.methodType(void.class, String.class));
Runnable r=(Runnable)LambdaMetafactory.metafactory(l, "run",
println.type().changeReturnType(Runnable.class), noArg, println, noArg)
.getTarget().invokeExact(System.out, "hello world");
r.run();
WeakReference<Class<?>> ref=new WeakReference<>(r.getClass());
ClassValueTest test=new ClassValueTest();
// compute and get
System.out.println(test.get(r.getClass()));
// verify that the value is cached, should not compute
System.out.println(test.get(r.getClass()));
// allow freeing
r=null;
System.gc();
if(ref.get()==null) System.out.println("collected");
// ensure that it is not our cache instance that has been collected
System.out.println(test.get(String.class));
}
}
在我的机器上打印出来:
hello world
computeValue
public void ClassValueTest$$Lambda$1/789451787.run()
public void ClassValueTest$$Lambda$1/789451787.run()
collected
computeValue
public boolean java.lang.String.equals(java.lang.Object)
为了解释,这个测试创建了一个匿名类,就像lambda表达式一样,可以进行垃圾回收。然后,它使用ClassValueTest
实例来缓存Method
的{{1}}对象。由于Class
个实例引用了它们的声明类,因此我们在这里指的是值的情况。
但是,在不再使用该类之后,它会被收集,这意味着已经收集了相关的值。因此,它对关键值的反向引用免疫。
使用另一个类的最后一个测试只是确保我们不是described here的急切垃圾收集的受害者,因为我们仍在使用缓存实例本身。
此类将单个值与类相关联,而不是每个线程的值,但应该可以将Method
与ClassValue
组合以获得所需的结果。
答案 1 :(得分:0)
我不确定你所说的问题。请查看:https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem
一些问题:
也许您可以尝试从您的问题中删除单词ThreadLocal,WeakReference,ConcurrentHashMap?
一些(疯狂的)猜测:
从我可以在行之间阅读的内容来看,在我看来,它是Java缓存的直接用例。例如。您可以使用Google Guava缓存并为显式清理添加删除侦听器。
由于资源不是线程安全的,因此您需要实现锁定机制。这可以通过将锁定对象放入缓存对象来完成。
如果您需要更多的并发性,请创建更多相同类型的资源,并使用线程的散列来增加缓存键,以模块化您想要的并发级别。
答案 2 :(得分:0)
我建议完全摆脱ThreadLocal
和WeakReference
的东西,因为正如你所说,资源没有绑定到特定的线程,它们不能同时从多个线程访问。 / p>
而是拥有全局缓存Map <Key, Collection <Resource>>
。缓存仅包含目前可以免费使用的资源。
线程首先会从缓存中请求可用资源。如果存在(当然,这应该是同步的,因为缓存是全局的),从该集合的该集合中删除任意资源并将其提供给该线程。否则,将构建一个新密钥用于该密钥。
当线程使用资源完成时,它应该将其返回到缓存,即添加到映射到资源键的集合。从那里它可以被同一个线程再次使用,甚至可以被不同的线程使用。
优点:
缓存是全局的,在应用程序退出时会关闭所有已分配的资源。
几乎没有任何内存泄漏的可能性,代码应该非常简洁。
线程可以共享资源(假设他们在不同时间需要相同的资源),可能会减少需求。
缺点:
需要同步(但可能很便宜且不难编码)。
也许还有其他人,具体取决于你究竟做了什么。
答案 3 :(得分:0)
在研究弱并发地图的想法时,我发现它是在Guava的Cache
中实现的。
我使用当前线程作为弱键,并提供CacheLoader
来自动为每个新线程创建资源。
还添加了一个删除侦听器,以便在Thread
对象为GC后或在关闭期间调用invalidateAll()
方法时自动清理每个线程的资源。
上面的大多数配置也可以在一个衬里(使用lambdas)中完成。