CDI |申请/相关范围|内存泄漏 - javax.enterprise.inject.Instance <t>未收集垃圾

时间:2018-03-20 15:49:47

标签: java-ee memory-leaks garbage-collection cdi tomee

我在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);
}

现在这很可能是由与@ApplicationScopedInstance<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)的情况下泄露的类是ExampleObjectWorker<T>Task<T>的实现。为了保持异步设计,我尝试在其实例中传递Worker<T>的实例(可能是一个坏主意,但我还是尝试过),调用destroy(T instance)并设置ExampleObjectnull。这清理了Task<T>实施和ExampleObject,但没有清除Worker<T>

我尝试的另一项测试是在MessageListenerImpl内进行同步设计(即删除Worker<T>并使用Task<T>)作为后备工作,调用destroy(T instance)进行清理。此STILL留下了泄漏,这使我相信它与@ApplicationScopedInstance<T>发生了冲突。

如果有一种方法可以保持异步设计,同时不会出现内存泄漏,请告诉我。真的很感激反馈。谢谢!

2 个答案:

答案 0 :(得分:3)

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

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

规范说,当它们的“父”(意思是它们被注入的对象)被破坏时,依赖范围的对象被销毁;但应用程序范围的bean与应用程序一样长,并保持它们创建的所有依赖范围的bean。这是内存泄漏。

为了缓解链接文章中所写的内容:

  1. 在您不再需要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会注意重新创建它,因此应用程序不会受到任何功能损害。仍然只会被实例化一次的资源将不断被破坏/重新创建,这可能具有非功能性(性能)影响。因此,从我的观点来看,这个解决方案会导致客户端和实现之间不必要的耦合,我宁愿不去实现它。

  2. 如果您只使用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();
    
    ...
    
  3. 为您的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;
            });
    }
}