【问题标题】:Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread线程“主”java.lang.OutOfMemoryError 中的异常:无法创建新的本机线程
【发布时间】:2017-06-22 07:51:56
【问题描述】:

我正在使用Formscanner,它在处理一些图像后给出了错误:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:950)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1357)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at com.albertoborsetta.formscanner.api.FormTemplate.findPoints(FormTemplate.java:852)
at com.albertoborsetta.formscanner.model.FormScannerModel.analyzeFiles(FormScannerModel.java:562)
at com.albertoborsetta.formscanner.main.FormScanner.main(FormScanner.java:145)

找点方法如下:

public void findPoints(BufferedImage image, int threshold, int density,
        int size) throws FormScannerException {
    height = image.getHeight();
    width = image.getWidth();
    int cores = Runtime.getRuntime().availableProcessors();

    ExecutorService threadPool = Executors.newFixedThreadPool(cores - 1);
    HashSet<Future<HashMap<String, FormQuestion>>> fieldDetectorThreads = new HashSet<>();

    HashMap<String, FormQuestion> templateFields = template.getFields();
    ArrayList<String> fieldNames = new ArrayList<>(templateFields.keySet());
    Collections.sort(fieldNames);

    for (String fieldName : fieldNames) {
        Future<HashMap<String, FormQuestion>> future = threadPool.submit(new FieldDetector(threshold, density, size, this, templateFields.get(fieldName), image));
        fieldDetectorThreads.add(future);
    }

    for (Future<HashMap<String, FormQuestion>> thread : fieldDetectorThreads) {
        try {
            HashMap<String, FormQuestion> threadFields = thread.get();
            for (String fieldName : threadFields.keySet()) {
                FormQuestion field = threadFields.get(fieldName);
                fields.put(fieldName, field);
                for (Entry<String, FormPoint> point : field.getPoints().entrySet()) {
                    if (point.getValue() != null) {
                        pointList.add(point.getValue());
                    }
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new FormScannerException(e.getCause());
        }
    }

    threadPool.shutdown();

}

上面的函数在循环中被调用并且java进程的数量增长并且在某个点上它引发了上面的异常。

在调用关闭方法后,这些线程是否会被杀死。我不是Java开发人员。我做了一些研发。但我没有成功。

【问题讨论】:

  • 您运行的是 32 位还是 64 位 JVM?使用的操作系统是什么?内存设置是什么(Xmx、Xms、Xss...)?
  • 假设池限制了运行的线程数,并且您有一个 thread.get 等待每个线程结束。因此,除非您在新线程中调用此方法(这将在堆栈跟踪中可见),否则我看不出这会如何重载。 当然,它可能是第一个运行的 FieldDetectors 实例,它会占用所有可用内存。 FieldDetector 实例会做什么?
  • 我使用 ubuntu 64bit 和 openjdk
  • @AxelH 我不知道。
  • 我看到你在他们的谷歌论坛上发布了一个问题。在那里搜索,我注意到有一个帖子here 谈论只有在应用程序关闭时才清理内存使用的问题。所以我会说API有内存泄漏,以前的文件没有正确释放,导致你在很多文件处理后出现问题。它是开源的,所以如果你有时间,我可以在这个周末尝试检查源代码。我现在对 OMR 很好奇 ;)

标签: java multithreading pid


【解决方案1】:

问题来自 Set&lt;Future&gt; 用于保存每个实例以供以后检查。

在聊天中,您告诉我您正在检查 120.000 个文件。这意味着创建了那么多的Future,当pool找到一个slot时,它会创建一个Thread来执行Callable。

由于Set 持有每个实例,Thread 不会被垃圾收集,这就是泄漏的原因。您需要删除所有使用过的Future,让GC 为下一个Thread 清除内存。

使用迭代器而不是循环本身很简单,您可以在使用前删除当前实例

Iterator<Future<HashMap<String, FormQuestion>>> iterator = fieldDetectorThreads.iterator();
while (iterator.hasNext()) {
    //get the next instance
    Future<HashMap<String, FormQuestion>> thread = iterator.next();
    //Remove it from the set
    iterator.remove();

    //then work on that instance just like before
    try {
        HashMap<String, FormQuestion> threadFields = thread.get();
        for (String fieldName : threadFields.keySet()) {
            FormQuestion field = threadFields.get(fieldName);
            fields.put(fieldName, field);
            for (Entry<String, FormPoint> point : field.getPoints().entrySet()) {
                if (point.getValue() != null) {
                    pointList.add(point.getValue());
                }
            }
        }
    } catch (InterruptedException | ExecutionException e) {
        throw new FormScannerException(e.getCause());
    }
}

此解决方案未经测试,但应该能够足够快地释放内存。

但是,如果提交请求的循环需要很长时间才能结束(在检查第一个请求之前生成 120k 未来),则在发送每个请求之前都会中断。

在这种情况下,可能需要将该逻辑拆分为两个线程,一个用于发送请求,一个用于检查结果,直到第一个线程结束并且集合为空。


以防万一,我会在循环之后添加一个关闭请求

threadPool.shutdown();

这应该没有必要,但奇怪的是我的测试程序没有它就不会结束......即使每个线程都已被处理,它们似乎仍然存在并阻塞了主线程。

【讨论】:

  • 我已经恢复了代码,当我处理了 1000 张纸时,它具有相同的效果 513 个进程正在运行,因为我根据您的建议进行了更改。
猜你喜欢
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-15
  • 1970-01-01
相关资源
最近更新 更多