gpt4 book ai didi

Java 代码在 AWS 上耗尽了空间内存,但在 MacOSX 上没有

转载 作者:搜寻专家 更新时间:2023-11-01 02:59:23 24 4
gpt4 key购买 nike

我需要另外一双眼睛看这个。

我已经用这个确切的代码将一个 zip 文件写成数百 GB,在 MacOSX 上没有本地修改。

使用 100% 未更改的代码,只是部署到运行 Ubuntu 的 AWS 实例,同样的代码会遇到内存不足问题(堆空间)。

这是正在运行的代码,将 MyBatis 流式传输到磁盘上的 CSV 文件:

File directory = new File(feedDirectory);
File file;
try {
file = File.createTempFile(("feed-" + providerCode + "-"), ".csv", directory);
} catch (IOException e) {
throw new RuntimeException("Unable to create file to write feed to disk: " + e.getMessage(), e);
}

String filePath = file.getAbsolutePath();
log.info(String.format("File name for %s feed is %s", providerCode, filePath));

// output file
try (FileOutputStream out = new FileOutputStream(file)) {
streamData(out, providerCode, startDate, endDate);
} catch (IOException e) {
throw new RuntimeException("Unable to write feed to file: " + e.getMessage());
}

public void streamData(OutputStream outputStream, String providerCode, Date startDate, Date endDate) throws IOException {
try (CSVPrinter printer = CsvUtil.openPrinter(outputStream)) {
StreamingHandler<FStay> handler = stayPrintingHandler(printer);
warehouse.doForAllStaysByProvider(providerCode, startDate, endDate, handler);
}
}

private StreamingHandler<FStay> stayPrintingHandler(CSVPrinter printer) {
StreamingHandler<FStay> handler = new StreamingHandler<>();
handler.setHandler((stay) -> {
try {
EXPORTER.writeStay(printer, stay);
} catch (IOException e) {
log.error("Issue with writing output: " + e.getMessage(), e);
}
});
return handler;
}

// The EXPORTER method
import org.apache.commons.csv.CSVPrinter;
public void writeStay(CSVPrinter printer, FStay stay) throws IOException {
List<Object> list = asList(stay);
printer.printRecord(list);
}

List<Object> asList(FStay stay) {
List<Object> list = new ArrayList<>(46);
list.add(stay.getUid());
list.add(stay.getProviderCode());
//....
return list;
}

这是我在本地运行时的 JVM 堆空间图(使用 jvisualvm)。我一直在本地使用 Java 8(jdk1.8.0_51 和 1.8.0_112)运行它,并取得了很好的结果。甚至写出 TB 的数据。

Notice heap looks great

^ 在上面,最大堆空间设置为 4 gigs,它增加的最多为 1.5 gigs,然后回落到 500 MB 左右,同时按预期将数据流式传输到 CSV 文件。

但是,当我在带有 jdk 1.8.0_111 的 Ubuntu 上运行它时,完全相同的操作将无法完成,堆空间不足(java.lang.OutOfMemoryError: Java heap space)

我已将 Xmx 值从 8 gigs 提高到 16 gigs 到 25 gigs,但仍然耗尽了堆空间。同时......文件的总大小总共只有 10 Gigs......这真的让我感到困惑。

这是 JVisualVm 图在 Ubuntu 盒子上的样子:

Same code, same operation

我毫不怀疑这是在两种环境中运行的完全相同的代码,在每个环境中执行相同的操作(提供相同数据的相同数据库服务器)

在这一点上,我能想到的唯一区别是:
  • 操作系统 - Ubuntu 与 Mac OS X
  • AWS 中的托管 VM 与硬金属笔记本电脑
  • AWS 中数据库和 Ubuntu 服务器之间的网络速度更快
  • JDK 版本在 Ubuntu 中是 1.8.0_111,本地试过 1.8.0_51 和 1.8.0_112

  • 任何人都可以帮助阐明这个问题吗?

    更新

    我试过用显式刷新/关闭语句替换所有“try-with-resources”语句,但没有成功。

    更重要的是,我一开始看到数据进来,我就试图在 Ubuntu 机器上强制进行垃圾收集,但它没有效果——肯定有什么东西阻止了在 Ubuntu 机器上收集堆......在 OS X 上运行完全相同的代码时,让我再次编写完整的辣酱 Jade 米饼馅没问题。

    更新 2

    除了上述环境的差异之外,我能想到的唯一其他差异是,AWS 中的服务器之间的连接是否如此之快,以至于它传输数据的速度比将数据刷新到磁盘的速度还要快……但这仍然没有解释我总共只有 10 gigs 数据的问题,它炸毁了 JVM 的 20 gigs 堆空间。

    是否有可能在 Ubuntu/Java 级别存在错误?

    更新 3

    尝试替换 CSVPrinter 的输出使用完全独立的库(OpenCSV 的 CSVWriter 代替 Apache 的 CSV 库)并且会出现相同的结果。

    一旦这段代码开始从数据库接收数据,堆就开始爆炸,垃圾收集器无法回收任何内存……但仅限于 Ubuntu。在 OS X 上,所有东西都会立即回收,堆永远不会增长。

    我也尝试在每次写入后刷新流,但也没有运气。

    更新 4

    得到了要打印的堆转储,因此我应该查看数据库驱动程序。特别是亚马逊红移驱动程序中的 InboundDataHandler。

    我正在使用带有自定义结果处理程序的 myBatis。我尝试将结果处理程序设置为在获得结果时有效地不执行任何操作(new ResultHandler<>() {//重写的方法实际上什么都不做})并且我知道我没有保留那里的任何引用。

    既然是 AWS/Redshift 定义的 InboundDataHandler ……这让我觉得它可能低于 myBatis 级别……要么:
  • 我正在设置的 SqlSessionFactory 中的错误
  • Redshift 驱动程序中仅在 Ubuntu/AWS 中弹出的错误
  • 我覆盖的结果处理程序中的错误

  • 这是堆转储屏幕截图:
    heap dump screenshot

    这是我设置 SqlSessionFactoryBean 的地方:
     @Bean
    public javax.sql.DataSource redshiftDataSource() throws ClassNotFoundException {
    log.info("Got to datasource config");
    // Dynamically load driver at runtime.
    Class.forName(dataWarehouseDriver);
    DataSource dataSource = new DataSource();
    dataSource.setURL(dataWarehouseUrl);
    dataSource.setUserID(dataWarehouseUsername);
    dataSource.setPassword(dataWarehousePassword);
    return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws ClassNotFoundException {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(redshiftDataSource());
    return factoryBean;
    }

    这是我作为测试运行的 myBatis 代码,以验证它不是我持有 ResultHandler 中的记录:
    warehouse.doForAllStaysByProvider(providerCode, startDate, endDate, new ResultHandler<FStay>() {
    @Override
    public void handleResult(ResultContext<? extends FStay> resultContext) {
    // do nothing

    }
    });

    有没有办法可以强制 SQL 连接不卡在记录或其他东西上?我将再次重申,在我的本地机器上,此内存泄漏没有问题……它仅在托管 AWS 环境中运行代码时才会出现。在这两种情况下,数据库驱动程序和服务器是相同的。

    更新 6
    我认为它终于修复了。感谢所有为我指明堆转储方向的人。这在很大程度上有助于将其缩小到有问题的类别。

    之后,我对 AWS redshift 驱动程序进行了一些研究,它明确指出您的客户应该为大数据的任何操作指定限制。所以我在 myBatis 配置中发现了如何做到这一点:
    <select id="doForAllStaysByProvider" fetchSize="1000" resultMap="FStayResultMap">        
    select distinct
    f_stay.uid,

    这成功了。

    请注意,即使在处理从 AWS 远程下载的更大的数据集(AWS 中的数据库,在家中的笔记本电脑上执行的代码)时,这也不是必需的,而且这应该不是必需的,因为我正在覆盖 myBatis ResultHandler<>单独处理每一行,从不保留任何对象。

    然而,仅当 AWS redshift jdbc 驱动程序在 AWS 中运行(aws 中的数据库,AWS 实例中执行的代码)时,AWS redshift jdbc 驱动程序才会发生一些奇怪的事情,这会导致此 InboundDataHandler 永远不会释放其资源,除非指定了 fetchSize。

    这是现在运行的服务器的堆,它比 AWS 中的任何时候都更远,堆空间从未超过 500Mb,在我在 jvisualvm 中点击“force gc”后,它显示“已用”堆小于100MB:

    it works

    再次非常感谢所有帮助指导这件事的人!

    最佳答案

    终于想出了解决办法。

    堆转储是最大的帮助——它表示 InboundDataHandler类亚马逊的 RedShift/postgres JDCB 驱动程序是罪魁祸首。

    设置 SqlSession 的代码看起来是合法的,所以转到 Amazon's documentation降落这颗 gem :

    To avoid client-side out-of-memory errors when retrieving large data sets using JDBC, you can enable your client to fetch data in batches by setting the JDBC fetch size parameter.



    我们之前没有遇到过这种情况,因为我们使用自定义 ResultHandlers 流式传输结果在 MyBatis 中......但是当 AWS Redshift JDBC 驱动程序在 AWS 本身上运行与外部 AWS 连接时,似乎有些不同。

    根据文档中的指导,我们在 MyBatis 选择查询中添加了一个“fetchSize”:
    <select id="doForAllStaysByProvider" fetchSize="1000" resultMap="FStayResultMap">        
    select distinct
    f_stay.uid,

    瞧!一切顺利。这是我们所做的唯一更改,堆从未超过几百 MB。

    您可以在上面的其中一个图表中看到堆脱离图表,一旦在 Amazon 上开始接收数据,堆就会直线上升,一旦开始就永远不会回收一盎司的堆空间。

    我的猜测是 Redshift JDBC 驱动程序在 Amazon 环境中进行某种优化时正在做一些不同的事情……这就是我能想到的解释行为的全部内容。

    显然亚马逊知道发生了什么,因为他们预先记录了它。我可能不知道正在发生的事情的全部“原因”,但至少一切都以一种令人满意的方式解决了。

    感谢所有帮助过的人。

    关于Java 代码在 AWS 上耗尽了空间内存,但在 MacOSX 上没有,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40413969/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com