【问题标题】:Does Spring Batch release the heap memory after processing each batch?Spring Batch 是否在处理完每个批次后释放堆内存?
【发布时间】:2020-04-24 06:03:26
【问题描述】:

我有一个 Spring 批处理作业,其中仅包含一个步骤,即使用 FlatFileItemReader 读取 CSV 文件(包含大约 2000 行)并将对象写入数据库。我有自己的自定义BeanWrapperFieldSetMapper,它将行映射到对象。块大小设置为 50,因此我期望在从每批(50 个对象)中写入对象后,这些对象的堆内存将被释放。

由于我正在利用批处理,我预计在每个给定时间只有 50 个 CreditCardDebt 对象。但相反,在处理最后一批时,我发现堆内存包含 2000 个 CreditCardDebt 对象。

我错过了什么?

我的 BeanWrapperFieldSetMapper 实现:

@Component("CREDIT_CARD_DEBT_FIELD_SET_MAPPER_TEST")
public class TestDebtFieldSetMapper extends BeanWrapperFieldSetMapper<CreditCardDebt> {

    public TestDebtFieldSetMapper() {
        super.setPrototypeBeanName("CREDIT_CARD_DEBT_FIELD_SET_MAPPER_TEST");
    }

    @NonNull
    @Override
    public CreditCardDebt mapFieldSet(FieldSet fieldSet) {
        CreditCardDebt creditCardDebt = new CreditCardDebt();
        creditCardDebt.setAccount(fieldSet.readString(0));
        creditCardDebt.setCardholderId(fieldSet.readString(1));
        creditCardDebt.setDueDate(convertToLocalDateViaInstant(fieldSet.readString(2)));
        creditCardDebt.setDaysPastDue(fieldSet.readInt(3));
        creditCardDebt.setOverdueAmount(fieldSet.readDouble(4));
        creditCardDebt.setDirectDebitMinimumPayment(fieldSet.readDouble(5));
        creditCardDebt.setDirectDebitBalance(fieldSet.readDouble(6));
        creditCardDebt.setDirectDebitStatus(fieldSet.readChar(7));
        creditCardDebt.setDirectDebitType(DirectDebitType.valueOf(fieldSet.readString(8)));
        creditCardDebt.setCreatedDate(LocalDateTime.now());
        creditCardDebt.setFileName("BAL");
        return creditCardDebt;
    }

    private LocalDate convertToLocalDateViaInstant(String dateToConvert) {
        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyyMMdd");
        return LocalDate.parse(dateToConvert, formatters);
    }

【问题讨论】:

  • 无论对象是否立即释放,垃圾收集器都会做实际释放内存的工作。它将决定何时运行,以及将什么留在原地与实际释放 - 根据运行时情况,有时决定是将垃圾留在原地。
  • 问题是,如果我使用更大的输入(140k 行)测试作业并将块大小设置为 1000,将堆内存大小限制为 64MB 以强制 GC操作,在处理前 3 个批次后,我得到一个 java.lang.OutOfMemoryError: GC overhead limit exceeded. 所以看起来,属于一个批次的实例在完成该批次的作业后没有被清理。这是预期的行为吗?
  • 处理过的块的项目应该被垃圾收集(我添加了一个包含更多细节的答案)。您确定您的自定义 BeanWrapperFieldSetMapper(或其他东西)在整个作业执行期间没有保存项目吗?
  • 我在问题描述中添加了我的 BeanWrapperFieldSetMapper 的实现。
  • 您好,我也有同样的问题,请问您找到答案了吗?

标签: java spring spring-boot spring-batch


【解决方案1】:

这留给垃圾收集器。与此问题相关的代码部分在ChunkOrientedTasklet 中。在ChunkOrientedTasklet最基本的形式中,有两个调用:

Chunk<I> inputs = chunkProvider.provide(contribution);
chunkProcessor.process(contribution, inputs);

ChunkProvider 使用ItemReader 读取commit-interval 项目(如果项目阅读器返回null,则更少)。而ChunkProcessor 使用ItemProcessorItemWriter 来处理和写入项目:

Chunk<O> outputs = transform(contribution, inputs);
write(contribution, inputs); // details of adjustments of output omitted here

此过程重复运行,直到数据源耗尽。因此,当 GC 启动时,应该对已处理块的项目进行垃圾收集(因为变量 inputs/outputs 被重复使用),除非在整个作业执行期间有东西将它们保存在内存中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-29
    • 1970-01-01
    • 2021-12-28
    • 2013-07-02
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    相关资源
    最近更新 更多