【问题标题】:improve java code to avoid Heap out of memory exception改进java代码以避免Heap out of memory异常
【发布时间】:2012-03-09 03:17:14
【问题描述】:

我有以下一段代码,我正在使用 stringBuffer 对象读取一个大文件,通过创建临时 byte[] 对象执行一些操作,因此,当文件大小很大时,我会遇到内存不足异常说 16 MB。

StringBuffer dataBuffer;
ArrayList<byte[]> sourceFragments;
ArrayList<BitSet> sourceBits = new ArrayList<BitSet>();
dataBuffer = eHelper.readFile(encoder.getFileName());
sourceFragments = eHelper.fragmentFile(dataBuffer.toString());
             /*
     * converting byte[] to BitSet
              the below loop is run 128 times
     */
    Iterator<byte[]> iter = sourceFragments.iterator();
    while (iter.hasNext()) {
        byte[] temp = iter.next();
                    // temp.length will return 128 KB
        sourceBits.add(eHelper.byteArrayToBitSet(temp));
    }

我想知道是否有办法防止这种内存不足异常的发生。我没有考虑增加堆空间的选项,我在 32 位机器上使用默认堆空间。有什么办法可以减少正在创建的临时对象的数量,这样我就可以避免 outOfMemory 异常

编辑1:

我对代码进行了以下更改,其中我将整个文件作为 String 加载到内存中,我不创建 byte[] 数组,而是直接从文件中读取并将其直接转换为 arrayList位集。这在我能够处理 20 MB 文件的情况下有所帮助,我想知道是否可以进一步推动这一点以处理最多 30 MB 的文件?

编辑2:

我修改了源代码如下,我删除了我创建的所有冗余数据类型 公共 ArrayList 片段源数据(文件文件名){ 随机存取文件 r ; ArrayList sourceBits = new ArrayList();

    try {
        r= new RandomAccessFile(filename, "r");
        System.out.println(r.length());
        encoder.setSourceFileLength((int)r.length());
        int fragmentSize = encoder.calculateFragmentSize();
        System.out.println(fragmentSize);
        encoder.setFragmentSize(fragmentSize);
        encoder.setParameters();

        byte[] b = new byte[fragmentSize] ;

            long new_pos=0;
            int i=0;
            while(new_pos<=encoder.getSourceFileLength()){
                i++;
                r.read(b ,0, fragmentSize );
                 new_pos=fragmentSize*i;
                 r.seek(new_pos);
                 sourceBits.add(BitSet.valueOf(b));
                  }
               r.close();
               b=null;

    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }


    return sourceBits;
}

}

【问题讨论】:

  • 为什么不创建BitSets 而不是创建byte[]?这将最大限度地减少内存和工作。即,即使您有内存,它也会更快。注意:一台 16 GB 的 PC 只需 1000 美元,所以也许是时候获得更多内存了。 ;)

标签: java exception heap-memory


【解决方案1】:

嗯,默认内存限制可能是 64M(这取决于您的 JVM),所以如果您将 16M 文件读入内存,然后将其转换为 List&lt;byte[]&gt; 也占用 16M,然后将其转换为 @ 987654322@ 也需要 16M,那么你肯定是在突破极限,因为可能还有其他事情需要一些内存。

dataBuffer 的内容转换为List&lt;byte[]&gt; 后,您可以将dataBuffer 显式设置为null。然后,在循环中,您可以不使用迭代器,而是以老式方式循环 List,允许您在将每个元素转换为 BitSet 后显式地将其设置为 null。内存压力应该会触发一个 GC 循环来清理这些未使用的数据结构。

【讨论】:

  • 有道理我会试试这个
【解决方案2】:

将整个文件读入字符串缓冲区似乎有点浪费内存,除非您以后需要它。

由于您将需要完整的 16MB(在内存中时甚至更多)来存储文件的位,因此您必须在生成阶段节省一些内存。

我不确定 eHelper 是什么类型的对象。 如果 sourcebits 必须是碎片化的,您可以尝试以下方法:

BITSET_MAX_SIZE = ...;

File file = new File("somefile");
int total = file.length();
InputStream in = new BufferedInputStream(new FileInputStream(file));
for (int bytesRead = 0; bytesRead < total;) {
    int currBitsetSize = Math.min(BITSET_MAX_SIZE, (total - read) * 8); // Can this be variable or should it be padded?
    BitSet bitset = new Bitset(currBitsetSize);
    for (int bitsetIndex = 0; bitsetIndex < currBitsetSize; bitsetIndex += 8) {
        int currByte = in.read();
        bytesRead++;
        for (int bitPos = 0; bitPos < 8; bitPos++) {
            if ((currByte & (1 << i)) > 0) {
                bitset.set(bitsetIndex + i); // Set the position to 1
            }
        }
    }
    sourceBits.add(bitset);
}
in.close();

我自己没有尝试过,但类似的方法可能会奏效。对不起,如果这不是最漂亮的例子。

也许你不能根据源直接使用新的 FileInputStream,但你应该得到一个输入流并从中逐字节读取。

这个代码肯定可以改进,因为它不是最有效的。您可能希望使用 in.read(byte[] buffer, int byteOffset, int byteCount) 进行读取。

【讨论】:

  • 我正在使用 RandomAccessFile 执行读取操作,无论如何感谢您的建议
【解决方案3】:

代码中有几个方面需要改进。让我们专注于循环。

Iterator<byte[]> iter = sourceFragments.iterator();
while (iter.hasNext()) {
   byte[] temp = iter.next();
   // temp.length will return 128 KB
   sourceBits.add(eHelper.byteArrayToBitSet(temp));
}

无需获取 ArrayList sourceFragments 的迭代器。您可以将 while 循环转换为 for 循环并简单地循环 ArrayList 中的每条记录。更改如下所示。

for(byte[] val : sourceFragments){
   sourceBits.add(eHelper.byteArrayToBitSet(val));
}

【讨论】:

  • for 循环也将隐式创建一个迭代器。此外,通常迭代器的实现方式是它们不分配新内存而是遍历底层集合。例如,列表上的迭代器可能只需要当前索引。
【解决方案4】:
  • 为什么不能增加内存?
  • 你在 dataBuffer 上所做的只是调用 toString,你可以尝试让 fragmentFile() 方法获取文件名并返回 sourceFragements,你将为数据缓冲区和在 toString( )。
  • 您也可以避免创建 sourceFragments 数组,而是尝试从您读取的每个 byte[] 直接创建 sourceBits。

【讨论】:

  • 如果有性能提升我会试试这个操作监视器
【解决方案5】:

看起来很明显,但为什么要将整个文件读入内存?为什么不一次读取 128KB?

fragmentFile 的作用也不明显。它如何将String转换为byte[]片段?

【讨论】:

  • 我在上述步骤之后执行了一系列操作,所以我无法一次读取 128KB
  • 这些操作需要什么数据?如果您不告诉我们您真正需要什么,我们就无法告诉您要摆脱什么。您将整个文件放在内存中三次:作为 StringBuffer、作为字节数组列表和作为 BitSet 列表 - 您实际上需要进一步处理其中哪些?
  • 我真的需要 bitSet 进行进一步处理,我已经更改了代码,以便我将文件作为字符串加载但从文件中读取并在执行过程中创建 bitSet。还有什么我可以注意的吗?
  • 你能发布新代码吗?包括任何函数(如fragmentFile)——不知道它是做什么的,很难知道你是否需要内存中的整个字符串。
猜你喜欢
  • 2019-06-23
  • 2018-08-06
  • 2018-08-01
  • 2019-01-09
  • 2017-04-24
  • 2019-06-18
  • 1970-01-01
  • 2021-09-02
  • 1970-01-01
相关资源
最近更新 更多