【问题标题】:CDI | Application / Dependent Scope | Memory Leak - javax.enterprise.inject.Instance<T> Not Garbage CollectedCDI |应用程序/从属范围 |内存泄漏 - javax.enterprise.inject.Instance<T> 未收集垃圾
【发布时间】:2018-03-20 15:49:47
【问题描述】:

我在 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&lt;T&gt; 的冲突引起的。我已经提供了一个示例,说明图层在我的类中是如何存在的。注意嵌套的Instance&lt;T&gt;。这是为了提供任务的动态注入。

外部类

@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&lt;T&gt;Task&lt;T&gt;的实现。为了保持异步设计,我尝试在它的实例中传递Worker&lt;T&gt; 的实例(可能是个坏主意,但我还是尝试了),调用destroy(T instance) 并将ExampleObject 设置为null。这清理了Task&lt;T&gt; 实现和ExampleObject,但不是Worker&lt;T&gt;

我尝试的另一个测试是在 MessageListenerImpl 中进行同步设计(即删除 Worker&lt;T&gt; 并使用 Task&lt;T&gt;)作为后备工作,调用 destroy(T instance) 进行清理。这仍然留下了漏洞,这让我相信这一定是与 @ApplicationScopedInstance&lt;T&gt; 的冲突。

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

【问题讨论】:

    标签: jakarta-ee memory-leaks garbage-collection cdi apache-tomee


    【解决方案1】:

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

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

    规范说,依赖作用域的对象在其“父对象”(即它们被注入的对象)被销毁时被销毁;但是应用程序范围的 bean 与应用程序一样长,保持它们创建的所有依赖范围的 bean 活着。这是内存泄漏。

    按照链接文章中的说明缓解操作:

    1. 当您不再需要taskWorker 时立即致电workerInstance.destroy(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 提供范围,以便其父级不再负责其生命周期,而是相关范围。

    【讨论】:

    • 答案写得很好。我只想强调行为(泄漏)不是错误,而是错误的使用/设计。使用Instance 时,您必须确保您的实例被销毁,尤其是在涉及@Dependent 范围时。
    【解决方案2】:

    所以,我找到了一个很好的实现 (source),它满足了我的用例。使用BeanManager 允许我控制任务bean 的生命周期。我避免使用Worker&lt;T&gt;,而是使用CompletableFuture&lt;T&gt;(对Task&lt;T&gt; 接口进行了细微更改以允许从任务返回值)。这允许我执行任务 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;
                });
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-05-10
      • 2012-01-13
      • 2012-06-28
      • 1970-01-01
      • 1970-01-01
      • 2016-11-02
      • 2011-10-22
      • 2012-01-03
      • 2012-05-30
      相关资源
      最近更新 更多