Spring Batch Jobs не освобождает память
Я выполняю около 18 000 весенних заданий параллельно, каждое с одним шагом. Каждый шаг состоит из чтения из файла, преобразования и манипулирования этими значениями и записи их в базу данных Mongo и MySql, ничего необычного. После завершения всех заданий потребление памяти остается на уровне 20 ГБ ИСПОЛЬЗОВАНО и остается там. Я строю свои пружинные элементы следующим образом:
@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);
}
}
}
Нужно ли как-то освобождать память или удалять задания после их завершения или что-то в этом роде? Я не понимаю, почему потребление памяти остается таким высоким.
С наилучшими пожеланиями
1 ответ
Брать эту информацию из htop и извлекать из нее что-либо - не очень хорошая идея. Это из-за управления памятью Java.
Java выделяет память из ОС и управляет этой памятью внутренне. Все это связано с такими терминами, как сборка мусора и модель памяти поколений.
По сути, если вы освобождаете память, удаляя ссылки на эти объекты в нашем приложении, память не освобождается сразу. Только если память, уже выделенная Java, заполнена, запускается цикл сбора мусора. Этот цикл не (обязательно) освобождает память от ОС. На первом этапе она сделает эту память доступной для вашей Java-программы, при этом сохраняя ее в отношении ОС.
Если эвристика в Java VM определяет, что у вас выделено слишком много памяти, она освободит память для ОС, но это то, от чего вы не должны зависеть.
Вот почему вы все еще видите 20G, зарезервированный для процесса Java. А без более тщательного изучения приложения вы даже не узнаете, освобождена ли эта память внутренне или заполнена мертвыми объектами.
Если вы хотите лучше понять объем памяти вашего приложения, я бы посоветовал вам сделать следующее: Инструменты, такие как JConsole или JVisualVM (здесь вам потребуется плагин Visual GC), позволяют вам проверять внутреннюю часть памяти, выделяемой Java VM. Внутри этой памяти находится строго область памяти, называемая старой или постоянной, все остальное не имеет отношения к вашему вопросу (ищите термин "управление памятью поколений", если вам интересно). Если вы хотите запустить сборку мусора для удаления тех объектов, которые уже мертвы (но еще не очищены), либо явно вызовите System.gc()
в вашем приложении или вызвать его через JConsole или JVisualVM (оба имеют кнопку для этого). Потребление памяти непосредственно после сборки мусора - это число, которое вы ищете в данный момент.