使用FixedThreadPool和ExecutorCompletionService时出现OutOfMemory错误

4
我正在开发一款应用程序,需要从数据库中获取用户列表并从目录(ldap或AD)更新其详细信息。我希望在多核机器上执行此过程,因此创建了这个应用程序(以下是代码)。我使用CompletionService并将结果存储在Future对象中。
但是过了一段时间后,我遇到了内存不足错误,并出现“无法创建新的本地线程”消息。在任务管理器中,我看到该应用程序创建了大量线程,但我要求创建大小等于处理器数量的固定线程池。
我的代码有什么问题?
class CheckGroupMembership {
public static void main(String[] args) throws Exception {

    final ExecutorService executor = Executors.newFixedThreadPool(**Runtime.getRuntime().availableProcessors()**);

    CompletionService<LdapPerson> completionService =
        new ExecutorCompletionService(executor)<LdapPerson>(executor);

    final int limit = 2000;

    DocumentService service1 = new DocumentService();
    List<String> userNamesList = service1.getUsersListFromDB(limit);

    List<LdapPerson> ldapPersonList = new ArrayList() <LdapPerson> (userNamesList.size());
    LdapPerson person;

    for (String userName : userNamesList) {
        completionService.submit(new GetUsersDLTask(userName));
    }

    try {
        for (int i = 0, n = userNamesList.size(); i < n; i++) {
            Future<LdapPerson> f = completionService.take();
            person = f.get();
            ldapPersonList.add(person);
        }
    } catch (InterruptedException e) {

        System.out.println("InterruptedException error:" + e.getMessage());
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    System.exit(0);
}
}

错误 CheckGroupMembership:85 - java.lang.OutOfMemoryError: 无法创建新的本地线程 java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: 无法创建新的本地线程 at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222) at java.util.concurrent.FutureTask.get(FutureTask.java:83

获取用户DLs任务

public class GetUsersDLTask implements Callable<LdapPerson> {
private String userName;

public GetUsersDLTask(String u) {
    this.userName = u;
}

@Override
public LdapPerson call() throws Exception {
    LdapService service = new LdapService();
    return service.getUsersDLs(userName);
}

}

1
请求从您的JVM获取线程转储。这些线程的名称是什么?LdapService是否创建了一个线程? - Emil Sit
LdapService不会创建任何线程。我正在使用标准的javax.naming.*工具。 - Vik Gamov
JVM在OOM之前的线程转储中包含什么?这些线程命名是什么? - Emil Sit
线程名称分别为Thread-2和Thread-3。 - Vik Gamov
3个回答

3
我很难相信你在GetUsersDLTask中没有创建线程(或至少是它的服务对象)。如果您查看堆栈跟踪,异常是从Future的get()方法抛出的。唯一可以设置此异常的方法是执行器调用Callabale.call()后。在call()方法中发生的任何可抛出的内容都将设置在Future的内部exception字段中。
例如:
Thread Pool: 
    Thread-1
      invoke call()
        call() 
          Create Thread
            throw OutOfMemoryError 
         propogate error to Thread pool
      set exception

否则,当你将请求提交到线程池时,这个异常会发生,而不是在你从未来中获取时。

是的,我相信你是对的。在GetUsersDLTask中,我创建了LdapService的新实例,每次都会创建InitialContext对象(并为与ldap进行套接字通信创建新线程)。因此,你对助记符的假设是正确的。感谢你的建议! - Vik Gamov

1

但是执行器是否同时实例化了其余48个线程?我如何控制线程的生成? - Vik Gamov
1
正如所提出的编辑所示,您提交任务,线程池管理线程。 - Emil Sit
1
为什么要踩我?你的内存不足是因为你创建了太多的线程 - 执行器会接受你提供的所有线程,但只会执行固定池中提供的数量。你可以控制线程的生成,因为你是将线程提交给执行器的人;你真的需要为每个用户名创建一个新线程吗?也许将它们分成100个一组或者更多,并将其提交到一个线程中。 - Noah
问题和评论是两个独立的想法。不用担心 :) - Noah
1
@sqrv 这不是真的。当您提交一个Callable/Runnable时,它将被放置在ES工作队列上。如果线程池是固定的,那么每个Runnable/Callable只有在有可用线程并可以轮询队列时才会被使用。如果您有一个Executors.newCachedThreadPool,那么是的,如果该线程没有处于空闲状态,则每个已提交的Callable都会生成一个新线程。但这不是这种情况。 - John Vint
显示剩余4条评论

0
你是否验证了固定线程池中创建的线程数量。也许可用处理器的数量过大了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接