gpt4 book ai didi

jakarta-ee - CDI |应用程序/从属范围 |内存泄漏 - javax.enterprise.inject.Instance 未收集垃圾

转载 作者:行者123 更新时间:2023-12-04 18:21:05 25 4
gpt4 key购买 nike

我在 TomEE Java 应用程序中使用 Instance 作为惰性/动态注入(inject)器,并且我注意到我的应用程序中存在内存泄漏。这对我来说是第一次,所以看到 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> .这是为了提供任务的动态注入(inject)。

外类
@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.
}

// ...

}

接口(interface)
public interface Task<T> {
void doTask();
void setObject(T obj);
}

未调用 destroy(T instance) 的类泄漏是 ExampleObject , Worker<T> ,以及 Task<T> 的实现.为了保持异步设计,我尝试传递 Worker<T> 的实例在它的实例中(可能是个坏主意,但我还是试过了),调用 destroy(T instance)和设置 ExampleObjectnull .这清理了 Task<T>实现和 ExampleObject ,但不是 Worker<T> .

我尝试的另一个测试是在 MessageListenerImpl 内进行同步设计。 (即删除 Worker<T> 并使用 Task<T> )作为后备工作,调用 destroy(T instance)清理。这仍然留下了泄漏,这让我相信这一定是与 @ApplicationScoped 的冲突。和 Instance<T> .

如果有办法在保持异步设计的同时没有内存泄漏,请告诉我。非常感谢反馈。谢谢!

最佳答案

确实这是 Instance 的弱点,它可能会泄漏。 This article有一个很好的解释。 (正如下面 Siliarus 的评论中所强调的,这不是 Instance 的固有错误,而是错误的使用/设计。)

您的 Worker声明没有范围,因此它是 @Dependent范围。这意味着每次注入(inject)都会重新创建它。 Instance.get()本质上是一种注入(inject),因此每次调用 get() 都会创建一个新的依赖范围对象。 .

规范说,依赖范围的对象在它们的“父对象”(意味着它们被注入(inject)的对象)被销毁时被销毁;但是应用程序范围的 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);
    }
    }

    编辑:关于这个选项的一些额外想法:如果随着时间的推移,注入(inject)的 bean 的实现从 @Dependent 更改会发生什么?例如@ApplicationScoped ?如果 destroy() call 没有显式删除,这不是毫无戒心的开发人员在正常重构中会做的事情,您最终会破坏“全局”资源。 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 提供范围,这样它的父级不再负责它的生命周期,而是相关的范围。
  • 关于jakarta-ee - CDI |应用程序/从属范围 |内存泄漏 - javax.enterprise.inject.Instance<T> 未收集垃圾,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49388648/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com