我在TomEE Java应用程序中使用Instance作为延迟/动态注入器,我注意到我的应用程序中存在内存泄漏。这对我来说是第一次,所以看到Java EE库中概述的内存泄漏警告实际上令人惊讶:
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>
。这是为了动态注入任务。
外类
@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 instance)
的情况下泄露的类是ExampleObject
,Worker<T>
和Task<T>
的实现。为了保持异步设计,我尝试在其实例中传递Worker<T>
的实例(可能是一个坏主意,但我还是尝试过),调用destroy(T instance)
并设置ExampleObject
到null
。这清理了Task<T>
实施和ExampleObject
,但没有清除Worker<T>
。
我尝试的另一项测试是在MessageListenerImpl
内进行同步设计(即删除Worker<T>
并使用Task<T>
)作为后备工作,调用destroy(T instance)
进行清理。此STILL留下了泄漏,这使我相信它与@ApplicationScoped
和Instance<T>
发生了冲突。
如果有一种方法可以保持异步设计,同时不会出现内存泄漏,请告诉我。真的很感激反馈。谢谢!
答案 0 :(得分:3)
确实这是Instance
的弱点,它可能会泄漏。 This article有一个很好的解释。 (正如下面Siliarus的评论中强调的那样,这不是Instance
的内在错误,而是错误的用法/设计。)
您的Worker
声明没有范围,因此它是@Dependent
作用域。这意味着每次进样都会重新创建 。 Instance.get()
本质上是一个注入,因此每次调用get()
时都会创建一个新的依赖范围对象。
规范说,当它们的“父”(意思是它们被注入的对象)被破坏时,依赖范围的对象被销毁;但应用程序范围的bean与应用程序一样长,并保持它们创建的所有依赖范围的bean。这是内存泄漏。
为了缓解链接文章中所写的内容:
在您不再需要workerInstance.destroy(taskWorker)
后立即致电taskWorker
,最好在finally
区块内:
@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会注意重新创建它,因此应用程序不会受到任何功能损害。仍然只会被实例化一次的资源将不断被破坏/重新创建,这可能具有非功能性(性能)影响。因此,从我的观点来看,这个解决方案会导致客户端和实现之间不必要的耦合,我宁愿不去实现它。
如果您只使用Instance
进行延迟加载,并且只有一个实例,则可能需要对其进行缓存:
...
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
提供范围,以便其父级不再对其生命周期负责,而是对相关范围负责。
答案 1 :(得分:1)
所以,我找到了一个很好的实现(source),满足了我的用例。使用BeanManager
允许我控制任务bean的生命周期。我避开Worker<T>
并改为使用CompletableFuture<T>
(对Task<T>
接口进行微小更改以允许从任务返回值)。这允许我执行任务bean的清理并异步处理任务中的任何异常。粗略的例子如下所示。感谢您的回复,我希望这可以帮助其他人解决这个问题!
外类
@ApplicationScoped
public class MessageListenerImpl implements MessageListener {
@Resource(name="example.mes")
private ManagedExecutorService mes;
@Inject
private BeanManager bm;
// ...
@Override
public void onMessage(Message message) {
CreationalContext<MyTask> ctx = bm.createCreationalContext(null);
Bean<?> beans = bm.resolve(bm.getBeans(MyTask.class));
MyTask task = (MyTask) bm.getReference(beans, MyTask.class, ctx);
task.setObject("Hello, Task!");
Utilities.doTask(mes, ctx, task);
}
// ...
}
已实施任务
public class MyTask implements Task<String, Boolean> {
private String obj;
// ...
@Override
public Boolean doTask() {
System.out.println(obj);
return Boolean.TRUE;
}
@Override
void setObject(String obj) {
this.obj = obj;
}
// ...
}
CompletableFuture
效用方法
public final class Utilities {
private Utilities() {
}
public static final doTask(ManagedExecutorService mes, CreationalContext ctx, Task task) {
CompletableFuture.supplyAsync((Supplier<Boolean>) task::doTask, mes)
.exceptionally((e) -> {
System.out.println("doTask : FAILURE : " + e.getMessage());
return Boolean.FALSE;
})
.thenApplyAsync((b) -> {
System.out.println("Releasing Context");
ctx.release();
return b;
});
}
}