Java代码耗尽AWS上的空间内存但不是MacOSX

时间:2016-11-04 01:31:53

标签: java amazon-web-services memory memory-leaks heap

我需要另外一套眼睛。

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

如果100%未更改的代码刚刚部署到运行Ubuntu的AWS实例,则相同的代码会遇到Out of Memory问题(堆空间)。

以下是正在运行的代码,将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文件,因为它&#39;应该是。

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

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

这里是JVisualVm图在Ubuntu框中的样子:

Same code, same operation

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

此时我能想到的唯一区别是:

  • 操作系统 - Ubuntu vs Mac OS X
  • AWS与硬金属笔记本电脑中的托管VM
  • AWS在数据库和Ubuntu服务器之间的网络速度更快
  • Ubuntu中JDK版本为1.8.0_111,本地尝试1.8.0_51和1.8.0_112

任何人都可以帮助解决这个问题吗?

更新

我已经尝试更换所有“尝试使用资源”&#39;带有显式刷新/关闭语句且没有运气的语句。

更重要的是,当我开始看到数据进入时,我试图在Ubuntu盒子上强制进行垃圾收集,但它没有任何效果 - 肯定会阻止堆被收集在Ubuntu机器上...在OS X上运行完全相同的代码让我再次编写完整的辣酱玉米饼馅没问题。

更新2

除了上述环境的差异之外,我能想到的唯一其他差异是,如果AWS中的服务器之间的连接如此之快,以至于它比将数据刷新到磁盘更快地流式传输数据......但这仍然无法解释我总共只有10演出数据的问题,并且它会耗尽20 Gigs的堆空间。

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

更新3

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

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

我在每次写作后都试过冲洗流,但也没有运气。

更新4

打开堆转储,根据这个我应该看看数据库驱动程序。特别是亚马逊的红移驱动程序中的InboundDataHandler。

我使用myBatis和自定义结果处理程序。我尝试将结果处理程序设置为在获得结果时有效地执行任何操作(新的ResultHandler&lt;&gt;(){//方法被覆盖以实际上没有任何内容})并且我知道我没有坚持任何引用。

因为它是由AWS / Redshift定义的InboundDataHandler ......它让我觉得它可能低于myBatis级别......:

  • SqlSessionFactory中的错误我设置
  • 只在Ubuntu / AWS中弹出的Redshift驱动程序中的错误
  • 我覆盖的结果处理程序中的错误

这里是堆转储截图: 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中的数据库,在家中的笔记本电脑上执行的代码),这也是不必要的,而且这不是必要的,因为我&#39 ; m重写myBatis ResultHandler&lt;&gt;它可以单独处理每一行,也不会保留任何对象。

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

这里是现在运行的服务器堆,比以前在AWS中更进一步,堆空间永远不会超过500Mb,并且在我点击强制gc&#39;在jvisualvm中,它显示了使用过的&#39;堆小于100mb:

it works

再次感谢所有帮助指导此事的人!

1 个答案:

答案 0 :(得分:3)

终于找到了解决方案。

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

设置SqlSession的代码看似合法,所以前往Amazon's documentation登陆了这个宝石:

  

在检索大数据时避免客户端内存不足错误   使用JDBC进行设置,您可以使客户端批量获取数据   通过设置JDBC提取大小参数。

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

根据文档中的指导,我们在MyBatis选择查询中添加了'fetchSize':

<select id="doForAllStaysByProvider" fetchSize="1000" resultMap="FStayResultMap">        
select distinct
    f_stay.uid,

瞧!一切都在游泳。这是我们做出的唯一改变,堆永远不会超过几百MB。

您可以在上面的图表中看到堆离开图表的情况,只要数据开始在亚马逊上接收,堆就会线性上升,并且一旦启动就不会回收一盎司的堆空间。

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

显然亚马逊知道自从他们提前记录之后发生了什么。我可能不知道发生了什么的充分“原因”,但至少一切都以一种令人满意的方式解决了。

感谢所有帮助过的人。