在完成Java Future时释放资源-此处:对Callable及其外部变量的引用

在完成Java Future时释放资源-此处:对Callable及其外部变量的引用,java,memory-management,concurrency,Java,Memory Management,Concurrency,在我的项目中,我经常使用Java Futures处理并发任务。在一个应用程序中,每个并发任务在完成过程中都需要相当大的内存块。由于其他一些设计选择,该内存在线程外部创建的对象中创建和引用(请参见下面更详细的示例) 令我惊讶的是,即使在未来任务(即其计算线程)已经完成之后,未来仍然拥有对该对象的引用。也就是说:如果没有其他对这个对象的引用被保存在其他地方,那么这个对象将不会被释放,除非将来被释放——即使任务已经完成 我天真的想法是,限制并发线程的数量会自动限制任务所拥有的资源(内存)的数量。这不是

在我的项目中,我经常使用Java Futures处理并发任务。在一个应用程序中,每个并发任务在完成过程中都需要相当大的内存块。由于其他一些设计选择,该内存在线程外部创建的对象中创建和引用(请参见下面更详细的示例)

令我惊讶的是,即使在未来任务(即其计算线程)已经完成之后,未来仍然拥有对该对象的引用。也就是说:如果没有其他对这个对象的引用被保存在其他地方,那么这个对象将不会被释放,除非将来被释放——即使任务已经完成

我天真的想法是,限制并发线程的数量会自动限制任务所拥有的资源(内存)的数量。这不是真的

考虑下面的代码。在本例中,我创建了一些任务。在计算过程中,ArrayList(外部变量)的大小会增加。该方法返回一个
向量
。即使任务已完成且ArrayList的作用域已保留,Future仍保留对ArrayList的引用(通过
FutureTask.sync.callable

总结如下:

  • FutureTask保存对可调用对象的引用,即使可调用对象已完成
  • 即使计算已经完成,Callable也会保存对计算期间使用的最终外部变量的引用
问:通过未来释放资源的最佳方式是什么?(当然,我知道可调用的局部变量在线程完成时被释放——这不是我想要的)

/*
*(c)版权所有Christian P.Fries,德国。版权所有。联系人:email@christianfries.com.
*
*创建于2013年8月17日
*/
包net.finmath.experiments.concurrency;
导入java.util.ArrayList;
导入java.util.Vector;
导入java.util.concurrent.Callable;
导入java.util.concurrent.ExecutionException;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入java.util.concurrent.Future;
导入java.util.concurrent.TimeUnit;
/**
*@作者克里斯蒂安·弗里斯
*
*/
公共类并发测试{
私有ExecutorService executor=Executors.newFixedThreadPool(10);
私有int numberOfDoubles=1024*1024/8;//1 MB
私有int numberOfModels=100;//100*1 MB
/**
*@param args
*@throws-ExecutionException
*@抛出中断异常
*/
公共静态void main(字符串[]args)引发InterruptedException、ExecutionException{
ConcurrencyTest ct=新的ConcurrencyTest();
ct.concurrencyTest();
}
/**
*@throws-ExecutionException
*@抛出中断异常
*/
public void concurrencyTest()引发InterruptedException、ExecutionException{
向量结果=getResults();
Runtime.getRuntime().gc();
System.out.println(“分配内存(仅结果):”+(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freemory());
}
私有向量getResults()引发InterruptedException,ExecutionException{
Vector resultsFutures=GetResultsConcurrent();
executor.shutdown();
执行人等待终止(1,时间单位:小时);
/*
*在这一点上,我们希望没有参考模型
*内存被释放。
*然而,未来仍然参考每个“模型”。
*/
Runtime.getRuntime().gc();
System.out.println(“分配内存(仅未来):”+(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freemory());
向量结果=新向量(resultsfourtures.size());

对于(inti=0;i,我想到的解决方案是

  • 使用完成服务中的期货(通过
    take()
    ),这将释放这些期货,然后在其他线程上执行此操作,返回新期货。外部代码仅引用完成服务中的期货
除了其他答案之外:另一种可能性是避免在callable中保留对模型的引用,如在sbat answers中。以下以不同的方式执行相同操作(受以下答案的启发):

  • 通过setter将对象“model”传递给匿名类的字段,完成后将该字段设置为零

        for(int i=0; i<models.size(); i++) {
    // REMOVED: final ArrayList<Double> model       = models.get(i);
            final int               modelNumber = i;
    
            Callable<Double> worker = new  Callable<Double>() {
    /*ADDED*/   private ArrayList<Double> model;
    /*ADDED*/   public Callable<Double> setModel(ArrayList<Double> model) { this.model = model; return this; };
    
                public Double call() throws InterruptedException {
    
                    /* ... */
    
                    /*
                     * Now the worker starts working on the model
                     */
                    double sum = 0.0;
    
                    /* ... */
    
    /*ADDED*/       model = null;
                    return sum;
                }
    /*ADDED*/   }.setModel(models.get(i));
    
    
            // The following line will add the future result of the calculation to the vector results
            results.add(i, executor.submit(worker));
        }
    

    for(int i=0;i如果您担心未来的任务会保留对模型的引用,您可以尝试替换

    final ArrayList<Double> model = models.get(i);
    

    至少你的“玩具示例”有所改进没有强制清除用户提供的模型。我不能说这是否可以应用于您的实际工作。

    如果您使用默认的
    ExecutionService
    实现,则
    Future
    本身就是一个
    FutureTask
    。FutureTask将保留对您提交的可运行或可调用的引用因此,这些资源分配的任何资源都将保留到未来不释放为止

    解决这类问题的最佳方法是将未来带入另一个只保留结果的未来,并将结果返回给调用者。这样,您就不需要完成服务或任何额外的黑客攻击

    // Decorate the executor service with listening
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    
    // Submit the task
    ListenableFuture<Integer> future = service.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            return new Integer(1234);
        }
    });
    
    // Transform the future to get rid of the memory consumption
    // The transformation must happen asynchronously, thus the 
    // Executor parameter is vital
    Futures.transform(future, new AsyncFunction<Integer, Integer>() {
        public ListenableFuture<Integer> apply(Integer input) {
            return Futures.immediateFuture(input);
        }
    }, service);
    
    //用侦听来装饰executor服务
    ListengExecutorService服务=MoreExecutors.ListengDecorator(Executors.newCachedThreadPool());
    //提交任务
    ListenableFuture=service.submit(new Callable()){
    公共整数调用()引发异常{
    返回新的整数(1234);
    }
    });
    //改变未来,摆脱内存消耗
    //转换必须异步进行,因此
    //执行器参数至关重要
    Futures.transform(future,new AsyncFunction(){
    public ListenableFuture应用(整数输入){
    返回期货。即时未来(输入);
    }
    },servi
    
    final ArrayList<ArrayList<Double>> modelList = 
                new ArrayList<ArrayList<Double>>();
    modelList.add(models.get(i));
    
    ArrayList<Double> model = modelList.get(0);
    
    model = null;
    modelList.clear();
    
    // Decorate the executor service with listening
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    
    // Submit the task
    ListenableFuture<Integer> future = service.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            return new Integer(1234);
        }
    });
    
    // Transform the future to get rid of the memory consumption
    // The transformation must happen asynchronously, thus the 
    // Executor parameter is vital
    Futures.transform(future, new AsyncFunction<Integer, Integer>() {
        public ListenableFuture<Integer> apply(Integer input) {
            return Futures.immediateFuture(input);
        }
    }, service);