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