2

我正在并行运行大约 18.000 个弹簧作业,每个作业都有一个步骤。每个步骤都包括从文件中读取、转换和操作这些值并将它们写入 Mongo 和 MySql 数据库,这没什么不寻常的。在所有作业完成后,内存消耗保持在 20GB USED并保持在那里。我按如下方式构建我的春季批次成员:

@Autowired
public ArchiveImportManager(final JobRepository jobRepository, final BlobStorageConfiguration blobConfiguration,
        final JobBuilderFactory jobBuilderFactory, final StepBuilderFactory stepBuilderFactory,
        final ArchiveImportSettings settings) {
    this.jobBuilderFactory = jobBuilderFactory;
    this.stepBuilderFactory = stepBuilderFactory;
    this.jobLauncher = new SimpleJobLauncher();
    final ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(THREAD_POOL_SIZE);
    threadPoolTaskExecutor.setMaxPoolSize(THREAD_POOL_SIZE);
    threadPoolTaskExecutor.setQueueCapacity(THREAD_POOL_QUEUE);
    threadPoolTaskExecutor.initialize();
    this.jobLauncher.setTaskExecutor(threadPoolTaskExecutor);
    this.jobLauncher.setJobRepository(jobRepository);
}

我创建一个工作如下:

private Job createImportJob(final ArchiveResource archiveResource, final int current, final int archiveSize) {

    final String name = "ImportArchiveJob[" + current + "|" + archiveSize + "]"
            + new Date(System.currentTimeMillis());
    final Step step = this.stepBuilderFactory
            .get(name)
            .<ArchiveResource, ArchiveImportSaveData> chunk(1)
            .reader(getReader(archiveResource, current, archiveSize))
            .processor(getProcessor(current, archiveSize))
            .writer(getWriter(current, archiveSize))
            .build();

    return this.jobBuilderFactory
            .get(name)
            .flow(step)
            .end()
            .build();

}

并循环启动所有作业:

private void startImportJobs(final List<ArchiveResource> archives) {
    final int size = archives.size();
    for (int i = 0; i < size; i++) {
        final ArchiveResource ar = archives.get(i);
        final Job j = createImportJob(ar, i, size);
        try {

            this.jobLauncher.run(j, new JobParametersBuilder()
                    .addDate("startDate", new Date(System.currentTimeMillis()))
                    .addString("progress", "[" + i + "|" + size + "]")
                    .toJobParameters());
        } catch (final JobExecutionAlreadyRunningException e) {
            log.info("Already running", e);
        } catch (final JobRestartException e) {
            log.info("Restarted", e);
        } catch (final JobInstanceAlreadyCompleteException e) {
            log.info("ALready completed", e);
        } catch (final JobParametersInvalidException e) {
            log.info("Parameters invalid", e);
        }
    }
}

我是否必须以某种方式释放内存或在作业完成后删除作业?我不明白为什么内存消耗保持那么高。

此致

4

1 回答 1

2

从 htop 获取该信息并从中派生任何东西并不是一个好主意。这是因为 Java 内存管理。

Java 从操作系统分配内存并在内部管理该内存。这都与垃圾收集和分代内存模型等术语有关。

基本上,如果您通过在我们的应用程序中删除对这些对象的引用来释放内存,则不会立即释放内存。仅当 Java 已分配的内存已满时,才会触发垃圾回收周期。该循环不会(必然)针对操作系统释放内存。第一步,它将使您的 Java 程序可以使用该内存,同时在操作系统方面仍然保留它。

如果 Java VM 中的启发式方法确定您分配了太多内存,它将向操作系统释放内存,但这是您不应该依赖的。

这就是为什么您仍然看到 Java 进程保留了 20G 的原因。如果不仔细查看应用程序内部,您甚至不知道该内存是在内部释放还是被死对象填满。

如果您想更好地了解应用程序的内存占用,我建议您执行以下操作: JConsole 或 JVisualVM 等工具(这里您需要 Visual GC 插件)允许您检查由Java 虚拟机。在该内存中,严格针对称为 old 或 tenured 的内存区域,其他所有内容都与您的问题无关(如果您好奇,请搜索术语世代内存管理)。如果您想触发垃圾收集以删除那些已经死掉(但尚未清理)的对象,System.gc()请在您的应用程序中显式调用或通过 JConsole 或 JVisualVM 触发它(两者都有一个按钮)。垃圾回收后直接消耗的内存是您当前正在寻找的数字。

于 2017-10-16T07:10:04.833 回答