Java 通过Spring批处理部分读取和写入数据-OutOfMemoryError:超出GC开销限制

Java 通过Spring批处理部分读取和写入数据-OutOfMemoryError:超出GC开销限制,java,garbage-collection,jvm,spring-batch,jvm-arguments,Java,Garbage Collection,Jvm,Spring Batch,Jvm Arguments,我正在运行一个带有spring批处理作业的应用程序。当我试图从一个数据源收集并发布一些数据到另一个数据源时,我得到以下异常 o.s.batch.core.step.AbstractStep - Encountered an error executing step upload in job reviewsToYtBatchJob java.lang.OutOfMemoryError: GC overhead limit exceeded at com.mysql.jdbc.Buffer.&l

我正在运行一个带有spring批处理作业的应用程序。当我试图从一个数据源收集并发布一些数据到另一个数据源时,我得到以下异常

o.s.batch.core.step.AbstractStep -  Encountered an error executing step upload in job reviewsToYtBatchJob
java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.mysql.jdbc.Buffer.<init>(Buffer.java:59)
at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:1967)
at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:3401)
at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:483)
at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:3096)
at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:2266)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1485)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:856)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2318)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
at org.springframework.batch.item.database.JdbcCursorItemReader.openCursor(JdbcCursorItemReader.java:126)
CHUNK_大小尝试从100到10000。如果我限制所选数据的大小,则不会超过堆大小

protected ItemReader<Review> reader() {
        JdbcCursorItemReader<Review> reader = new JdbcCursorItemReader<>();
        reader.setDataSource(dataScource);
        reader.setSql(
         //sql query
        );
        reader.setFetchSize(CHUNK_SIZE);
        reader.setRowMapper(
                (rs, rowNum) -> new Review(
                        rs.getLong("reviewId"),
                        //map data

                )
        );
        return reader;
    }

private ItemProcessor<Review, ReviewTo> processor() {
        return review -> new ReviewTo(
                //parameters
        );
    }
private ItemWriter<ReviewTo> writer() {
    return new ItemWriter<>(client);
}

private TaskletStep uploadStep() {
    SimpleStepBuilder<Review, ReviewTo> uploadStep = new SimpleStepBuilder<>(stepBuilderFactory.get("upload"));
    return uploadStep
            .chunk(CHUNK_SIZE)
            .reader(reader())
            .processor(processor())
            .writer(writer())
            .allowStartIfComplete(true)
            .build();
}

@Bean
public Job reviewsToYtBatchJob() {
    return jobBuilderFactory.get(JOB_NAME)
            .start(//generate table)
                    .build())
            .next(stepBuilderFactory.get("createTmpTable")
                    .tasklet(//step)
                    .build())
            .next(uploadStep())
            .next(stepBuilderFactory.get("moveTmpTableToDestination")
                    .tasklet(//step)
                    .build())
            .build();
}
protecteditemreader(){
JdbcCursorItemReader=新的JdbcCursorItemReader();
reader.setDataSource(DataSource);
reader.setSql(
//sql查询
);
reader.setFetchSize(块大小);
reader.setRowMapper(
(rs,rowNum)->新评论(
R.getLong(“reviewId”),
//地图数据
)
);
返回读取器;
}
专用项目处理器(){
退货审核->新审核WTO(
//参数
);
}
私有项目编写器(){
返回新的ItemWriter(客户端);
}
私有TaskletStep上载步骤(){
SimpleTestBuilder uploadStep=新的SimpleTestBuilder(stepBuilderFactory.get(“upload”);
返回上载步骤
.chunk(chunk\u大小)
.reader(reader())
.processor(处理器())
.writer(writer())
.allowStartIfComplete(真)
.build();
}
@豆子
公共作业ReviewToyBatchJob(){
返回jobBuilderFactory.get(作业名称)
.start(//生成表)
.build())
.next(stepBuilderFactory.get(“createTmpTable”)
.tasklet(//步骤)
.build())
.next(上载步骤())
.next(stepBuilderFactory.get(“moveTmpTableToDestination”)
.tasklet(//步骤)
.build())
.build();
}

块处理的整个思想是不将整个数据集加载到内存中,而是分块加载。因此,像您所做的那样使用面向块的步骤是一种方法

如何获取堆大小参数

这个参数就是您通过
-Xms
-Xmx
传递给JVM的参数。请参阅JVM文档中的默认值

如何部分获取数据

设置面向块的步骤时,Spring Batch将根据步骤的chunkSize和
JdbcCursorItemReader
的fetchSize自动执行。顺便说一句,我看到你为这两个参数设置了相同的值,这是一件好事!匹配fetchSize和chunkSize通常可以获得更好的性能


因此,我认为您的问题并不是真正的问题,因为增加块大小时,在出现OOM错误之前,内存中将加载更多的项是正常的。

内存空间不足。它使用参数CHUNK_SIZE=100000和-Xmx4g工作。有一个带有虚拟机参数的配置文件,我可以在其中增加堆大小。

谢谢您的回答!我使用一个远程虚拟机,想知道如何从文档中了解这些参数的值,而不是实际值。感谢您对chunkSize和fetchSize的评论。看起来我只需要为块大小选择一个合适的值。虽然我尝试了从10到700000的不同值。价值越大,失败越快。我猜is值太大,一次加载所有数据可能会有问题,如果值太小,可能会产生大量垃圾,因此GC无法使用可用内存。
protected ItemReader<Review> reader() {
        JdbcCursorItemReader<Review> reader = new JdbcCursorItemReader<>();
        reader.setDataSource(dataScource);
        reader.setSql(
         //sql query
        );
        reader.setFetchSize(CHUNK_SIZE);
        reader.setRowMapper(
                (rs, rowNum) -> new Review(
                        rs.getLong("reviewId"),
                        //map data

                )
        );
        return reader;
    }

private ItemProcessor<Review, ReviewTo> processor() {
        return review -> new ReviewTo(
                //parameters
        );
    }
private ItemWriter<ReviewTo> writer() {
    return new ItemWriter<>(client);
}

private TaskletStep uploadStep() {
    SimpleStepBuilder<Review, ReviewTo> uploadStep = new SimpleStepBuilder<>(stepBuilderFactory.get("upload"));
    return uploadStep
            .chunk(CHUNK_SIZE)
            .reader(reader())
            .processor(processor())
            .writer(writer())
            .allowStartIfComplete(true)
            .build();
}

@Bean
public Job reviewsToYtBatchJob() {
    return jobBuilderFactory.get(JOB_NAME)
            .start(//generate table)
                    .build())
            .next(stepBuilderFactory.get("createTmpTable")
                    .tasklet(//step)
                    .build())
            .next(uploadStep())
            .next(stepBuilderFactory.get("moveTmpTableToDestination")
                    .tasklet(//step)
                    .build())
            .build();
}