CDI |应用/相关范围|内存泄漏-javax.enterprise.inject.Instance< T>未收集垃圾 [英] CDI | Application / Dependent Scope | Memory Leak - javax.enterprise.inject.Instance<T> Not Garbage Collected

查看:90
本文介绍了CDI |应用/相关范围|内存泄漏-javax.enterprise.inject.Instance< T>未收集垃圾的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将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()

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:


  1. 一旦不再需要 taskWorker ,立即调用 workerInstance.destroy(taskWorker),最好在最终块内:

  1. Call workerInstance.destroy(taskWorker) as soon as you do not need the taskWorker anymore, preferably within a finally 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&lt; T&gt;未收集垃圾的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    相关文章
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆