如何在特定场景中避免OutOfMemory问题

时间:2018-01-22 10:34:16

标签: java out-of-memory

我们有后台作业,一次从一批1000条记录中取出特定表中的记录。 此作业以间隔30分钟运行。 现在, 这些记录包含电子邮件(密钥)和原因(值)。 问题是我们必须针对数据仓库查找这些记录(过滤类型的东西 - 从仓库获取最近180天的数据)。 由于调用数据仓库的时间非常昂贵(大约45分钟)。 所以,现有的情况是这样的。 我们检查磁盘上的平面文件。如果它不存在。我们打电话给数据仓库,获取记录(大小范围从 20万到25万) 并将这些记录写在磁盘上。 在后续调用中,我们只从平面文件中查找。 - 将整个文件加载到内存中并进行内存中搜索和过滤。 这导致了OutOfMemory问题很多次。

所以,我们修改了这样的逻辑。 我们以 4个等间隔修改了数据仓库调用,并使用ObjectOutputStream将每个结果再次存储在文件中, 现在,在随后的呼叫中,我们将数据加载到内存中,即0-30天,31-60天等等。 但是,这也没有帮助。专家们能否建议解决这个问题的理想方法是什么?高级团队中的某人建议使用CouchDB 来存储和查询数据。但是,乍一看我更愿意,如果有现有基础设施,任何好的解决方案都可用吗?如果没有,那么可以考虑使用其他工具。

截至目前为止过滤代码。

private void filterRecords(List<Record> filterRecords) {
long start = System.currentTimeMillis();
logger.error("In filter Records - start (ms) : "+start);
Map<String, String> invalidDataSet=new HashMap<String, String>(5000);
try{
    boolean isValidTempResultFile = isValidTempResultFile();
    //1. fetch invalid leads data from DWHS only if existing file is 7 days older.

    String[] intervals = {"0","45","90","135"};

    if(!isValidTempResultFile){
        logger.error("#CCBLDM isValidTempResultFile false");
        for(String interval : intervals){
            invalidDataSet.clear();
             // This call takes approx 45 minutes to fetch the data
            getInvalidLeadsFromDWHS(invalidDataSet, interval, start);
            filterDataSet(invalidDataSet, filterRecords);
        }
    }
    else{
        //Set data from temporary file in interval to avoid heap space issue
        logger.error("#CCBLDM isValidTempResultFile true");
        intervals = new String[]{"1","2","3","4"};
        for(String interval : intervals){
            // Here GC won't happen at immediately causing OOM issue.
            invalidDataSet.clear();
            // Keeps 45 days of data in memory at a time
            setInvalidDataSetFromTempFile(invalidDataSet, interval);
            //2. mark current record as incorrect if it belongs to invalid email list
            filterDataSet(invalidDataSet, filterRecords);
        }
    }
}catch(Exception filterExc){
    Scheduler.log.error("Exception occurred while Filtering Records", filterExc);
}finally{
    invalidDataSet.clear();
    invalidDataSet = null;
}

long end = System.currentTimeMillis();
logger.error("Total time taken to filter all records ::: ["+(end-start)+"] ms.");
}

2 个答案:

答案 0 :(得分:2)

我强烈建议略微更改您的基础架构。 IIYC你正在查找文件中的某些内容以及地图中的某些内容。使用文件很痛苦并且将所有内容加载到内存会导致OOME。使用内存映射文件可以做得更好,这样可以快速简单地访问。

Chronicle-Map为堆外存储的数据提供Map接口。实际上,数据驻留在磁盘上并根据需要占用主存储器。你需要制作你的钥匙和价值Serializable(你已经做过的AFAIK)或使用另一种方式(可能更快)。

它不是数据库,它只是一个ConcurrentMap,这使得使用它变得非常简单。整个安装只是添加一行compile "net.openhft:chronicle-map:3.14.5"build.gradle(或几个maven行)。

还有其他选择,但Chronicle-Map就是我尝试过的(刚开始使用它,但到目前为止一切都运行良好)。

还有Chronicle-Queue,如果你需要批处理,但我强烈怀疑你是否需要它,因为你受到磁盘而不是主存的限制。

答案 1 :(得分:1)

这是批处理类代码的典型用例。您可以引入一个新列,例如'isProcessed',其值为'false'。阅读说1-100行(其中id&gt; = 1&amp;&amp; id&lt; = 100),处理它们并将该标记标记为true。然后阅读说下一个100,依此类推。在作业结束时,再次将所有标志标记为false(重置)。但随着时间的推移,在这样的自定义框架上维护和开发功能可能变得困难。有开源替代品。

Spring批处理是这些用例的一个非常好的框架,可以考虑:https://docs.spring.io/spring-batch/trunk/reference/html/spring-batch-intro.html

还有Easy batch:https://github.com/j-easy/easy-batch,它不需要'Spring framework'知识

但如果它是一个巨大的数据集并且将来会继续增长,请考虑转向“大数据”技术堆栈