改进java代码以避免Heap内存不足异常

时间:2012-02-17 17:55:42

标签: java exception heap-memory

我有以下代码,我正在使用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 Exception

EDIT1:

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

edit2:

我已经修改了如下的源代码,我删除了我创建的所有冗余数据类型         public ArrayList fragmentSourceData(File filename){         RandomAccessFile 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;
}

}

5 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

  • 为什么不能增加内存?
  • 你在dataBuffer上所做的就是调用toString,你可以尝试使用fragmentFile()方法获取文件名并返回sourceFragements,你将为数据缓冲区和在toString中创建的字符串节省内存( )。
  • 您也可以避免创建sourceFragments数组,而是尝试从您阅读的每个byte []直接创建sourceBits。

答案 2 :(得分:0)

代码中有一些改进的地方。让我们关注循环。

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));
}

答案 3 :(得分:0)

嗯,默认内存限制可能是64M(取决于你的JVM),所以如果你正在将16M文件读入内存,那么将其转换为List<byte[]>也需要16M然后转换它变成List<BitSet>也需要16M,然后你肯定会推动极限,因为可能还有其他事情需要一些记忆。

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

答案 4 :(得分:0)

将整个文件读入stringbuffer似乎是浪费内存,除非你以后需要它。

由于您需要完整的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)读取。