CDI |应用/相关范围|内存泄漏-javax.enterprise.inject.Instance< T>未收集垃圾 [英] CDI | Application / Dependent Scope | Memory Leak - javax.enterprise.inject.Instance<T> Not Garbage Collected
问题描述
我将Instance用作TomEE Java应用程序中的惰性/动态注入器,并且我注意到我的应用程序中发生内存泄漏。这是我的第一次学习,因此看到Java EE库中概述的内存泄漏警告实际上令人惊讶:
I am using Instance as a lazy / dynamic injector in a TomEE Java application, and I have noticed a memory leak in my application. This is a first for me, so it's actually surprising to see a memory leak warning that has been outlined in the Java EE Library :
package javax.enterprise.inject;
public interface Instance<T> extends Iterable<T>, Provider<T>
{
/**
* Destroy the given Contextual Instance.
* This is especially intended for {@link javax.enterprise.context.Dependent} scoped beans
* which might otherwise create mem leaks.
* @param instance
*/
public void destroy(T instance);
}
现在,这很可能是与 @ApplicationScoped
和 Instance< T>
。我提供了一个有关班级中各层的示例。请注意嵌套的 Instance< T>
。这是为了动态注入任务。
Now this is most likely being caused by a clash with @ApplicationScoped
and the Instance<T>
. I've provided an example of how the layers are in my classes. Notice the nested Instance<T>
. This is to provide dynamic injection of tasks.
外部类
@ApplicationScoped
public class MessageListenerImpl implements MessageListener {
@Resource(name="example.mes")
private ManagedExecutorService mes;
@Inject @Any
private Instance<Worker<ExampleObject>> workerInstance;
// ...
@Override
public void onMessage(Message message) {
ExampleObject eo = new ExampleObject();
Worker<ExampleObject> taskWorker = workerInstance.get();
taskWorker.setObject(eo);
mes.submit(taskWorker);
}
// ...
}
内部类
public class Worker<T> implements Runnable {
@Inject @Any
private Instance<Task> taskInstance;
@Setter
private T object
// ...
@Override
public void run() {
Task t = taskInstance.get();
t.setObject(object);
t.doTask();
// Instance destruction, manual cleanup tried here.
}
// ...
}
接口
public interface Task<T> {
void doTask();
void setObject(T obj);
}
在不调用 destroy(T实例)
是 ExampleObject
, Worker< T>
和<$ c的实现$ c>任务< T> 。为了保持异步设计,我尝试在实例中传递 Worker< T>
的实例(可能是个坏主意,但我还是尝试了),调用了 destroy(T实例)
并将 ExampleObject
设置为 null
。这样可以清理 Task< T>
实现和 ExampleObject
,但不能清除 Worker< T>。
。
The classes that are leaking without calling destroy(T instance)
are ExampleObject
, Worker<T>
, and the implementation of Task<T>
. To keep the async design, I have tried passing the instance of Worker<T>
within it's instance (probably a bad idea, but I tried anyways), calling destroy(T instance)
and setting ExampleObject
to null
. This cleaned up the Task<T>
implementation and ExampleObject
, but not Worker<T>
.
我尝试的另一项测试是在 MessageListenerImpl
中进行同步设计(即删除 Worker< T>
并使用 Task< T>
)作为后备工作,调用 destroy (T实例)
进行清理。仍然留下了泄漏,这使我相信必须与 @ApplicationScoped
和 Instance< T>
。
Another test I tried was doing a synchronous design within MessageListenerImpl
(i.e. removing Worker<T>
and using Task<T>
) as a fallback effort, calling destroy(T instance)
to clean up. This STILL left the leak, which leads me to believe it's got to be the clash with @ApplicationScoped
and the Instance<T>
.
如果有一种方法可以保持异步设计而又不造成内存泄漏,请告诉我。非常感谢您的反馈。谢谢!
If there is a way to keep the async design while achieving no memory leaks, please let me know. Really appreciate feedback. Thanks!
推荐答案
实际上,这是 Instance
的弱点,可能泄漏。 本文有一个很好的解释。 (正如下面Siliarus的评论中强调的那样,这不是 Instance
的固有错误,而是错误的用法/设计。)
Indeed this is a weakness of Instance
, it may leak. This article has a good explanation. (As underlined in the comment from Siliarus below, this is not an intrinsic bug of Instance
, but wrong usage/design.)
您的 Worker
声明没有范围,因此它是 @Dependent
范围的。这意味着为每次注射重新创建。 Instance.get()
本质上是注入,因此每次调用 get()$ c $都会创建一个新的依赖范围对象。 c>。
Your Worker
declares no scope, thus it is @Dependent
scoped. This means it is created anew for each injection. Instance.get()
is essentially an injection, so a new dependent-scoped object is created with each invocation of get()
.
该规范说,依赖范围的对象在其父对象(即注入它们的对象)被破坏时会被破坏;但是应用程序范围的Bean的寿命与应用程序一样长,从而使它们创建的所有依赖范围的Bean都保持活动状态。这是内存泄漏。
The specification says that dependent-scoped objects are destroyed when their "parent" (meaning the object they are injected into) gets destroyed; but application-scoped beans live as long as the application, keeping all dependent-scoped beans they created alive. This is the memory leak.
要减轻链接文章中所写的行为:
To mitigate do as written in the linked article:
-
一旦不再需要
taskWorker
,立即调用workerInstance.destroy(taskWorker)
,最好在最终
块内:
Call
workerInstance.destroy(taskWorker)
as soon as you do not need thetaskWorker
anymore, preferably within afinally
block:
@Override
public void onMessage(Message message) {
ExampleObject eo = new ExampleObject();
Worker<ExampleObject> taskWorker;
try {
taskWorker = workerInstance.get();
taskWorker.setObject(eo);
mes.submit(taskWorker);
}
finally {
workerInstance.destroy(taskWorker);
}
}
编辑:关于此选项的想法:如果一段时间后注入的bean的实现从 @Dependent
更改为 @ApplicationScoped
?如果没有显式删除 destroy()
调用,这不是毫无疑问的开发人员在常规重构中所做的事情,那么您最终将破坏全局资源。 CDI将小心地重新创建它,因此不会对应用程序造成功能损害。仍然打算仅实例化一次的资源仍将不断销毁/重新创建,这可能会对功能(性能)产生影响。因此,从我的角度来看,此解决方案会导致客户端与实现之间不必要的耦合,我宁愿不这样做。
Some extra thoughts on this option: What happens if, in the course of time, the implementation of the injected bean changes from @Dependent
to e.g. @ApplicationScoped
? If the destroy()
call is not explicitly removed, which is not something an unsuspecting developer will do in a normal refactoring, you will end up destroying a "global" resource. CDI will take care to recreate it, so no functional harm will come to the application. Still a resource intended to be instantiated only once will be constantly destroyed/recreated, which might have non-functional (performance) implications. So, from my point of view, this solution leads to unnecessary coupling between the client and the implementation, and I would rather not go for it.
如果仅使用实例
进行延迟加载,并且只有一个实例,您可能希望对其进行缓存:
If you are only using the Instance
for lazy loading, and there is only one instance, you may want to cache it:
...
private Worker<ExampleObject> worker;
private Worker<ExampleObject> getWorker() {
if( worker == null ) {
// guard against multi-threaded access if environment is relevant - not shown here
worker = workerInstance.get();
}
return worker;
}
...
Worker<ExampleObject> taskWorker = getWorker();
...
将范围授予 Worker
,因此其父级不再对其生命周期负责,而是相关范围。
Give scope to your Worker
, so that its parent is no longer responsible for its lifecycle, but the relevant scope.
这篇关于CDI |应用/相关范围|内存泄漏-javax.enterprise.inject.Instance< T>未收集垃圾的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!