Java 通过Spring批处理部分读取和写入数据-OutOfMemoryError:超出GC开销限制
我正在运行一个带有spring批处理作业的应用程序。当我试图从一个数据源收集并发布一些数据到另一个数据源时,我得到以下异常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
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();
}