OutOfMemory多线程时的异常......堆空间?

时间:2014-11-28 19:10:02

标签: java android multithreading out-of-memory heap-memory

我正在开发一个需要与Android 2.3(Gingerbread)兼容的应用程序,而我用于开发测试的设备是运行Android 2.3.6的Motorola Atrix MB860。

在这个设备中,我获得了大约40MB的最大堆空间,据我所知,我的应用程序使用了大约33MB,但无论如何我得到OutOfMemoryError例外。

基本上,我的代码中与此问题相关的部分会产生一个大的String(8MB - 我知道它相当大,但是如果它太小则不能满足其中一个要求)然后去on创建2个线程,使用这样的字符串同时写入某个内存空间。

以下是代码:

    // Create random string
    StringBuilder sb = new StringBuilder();
    sb.ensureCapacity(8388608); // ensuring 8 MB is allocated on the heap for the StringBuilder object
    for (long i = 0; i < DATA_SIZE; i++) {
            char c = chars[new Random().nextInt(chars.length)];
            sb.append(c);
    }
    String randomByteString = sb.toString();

    ExecutorService executor = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 2; i++) {
        Runnable worker = new SlidingBubbles(param1, param2, randomByteString)
        executor.execute(worker);
    }
    // This will make the executor accept no new threads 
    // and finish all existing threads in the queue 
    executor.shutdown();

    // Wait until all threads are finish 
    while(!executor.isTerminated()) {
        // wait for bubble threads to finish working...
    }

和线程的例程:

private class SlidingBubbles implements Runnable {
    private int param1, param2;
    private String randomByteString;
    private final Object mSignal = new Object();
    private volatile long tempBytesWritten = 0;
    private volatile long totalBytesWritten = 0;

    public SlidingBubbles(int param1, int param2, String randomByteString) {
        this.param1= param1;
        this.param2= param2;
        this.randomByteString = randomByteString;
    }

    private void doIt() {
        File file = null;
        RandomAccessFile randomAccessFile = null;
        FileChannel fc = null;

        try {
            while(param1> 0) {
                // Instantiate the 1st bubble file
                file = new File(TARGET_DIR, String.valueOf(Calendar.getInstance().getTimeInMillis()));

                while(param2 > 0) {                        
                    randomAccessFile = new RandomAccessFile(file, "rwd");
                    fc = randomAccessFile.getChannel();

                    fc.position(fc.size());
                    synchronized (mSignal) {
                        tempBytesWritten = fc.write(ByteBuffer.wrap(randomByteString.getBytes()));

                        totalBytesWritten += tempBytesWritten;
                    }

       // some other things that don't matter
    }

    @Override
    public void run() {
        wipe();            
    }
}

尴尬(对我来说),在线程例程(tempBytesWritten = fc.write(ByteBuffer.wrap(randomByteString.getBytes()));)的第30行,第二个线程(“pool-1-thread-2”)启动异常,退出和第一个线程( “pool-1-thread-1”)继续(实际上,开始)正常执行。 当JVM完成为那个大String分配空间时,应用程序正在使用33MB的堆。正如您从代码中看到的那样,String仅创建一次,但随后从两个线程中多次使用。

线程不应只使用String的引用而不是复制它吗? (在这种情况下,这将超过40MB的津贴)。

我还必须指出,在Gingerbread(previous research)上增加这个堆空间是不可能的(或者至少似乎是,根据我的理解)。

有什么我想念的吗? 非常感谢任何帮助。

5 个答案:

答案 0 :(得分:1)

您可以静态获取8MB数据,但永远不会创建它的副本。在Android上,StringBuilderchar[]共享内部String,但String#getBytes()每次都会创建数据副本。

我认为你的角色是纯ASCII,当它们更特殊时,它不能正常工作。

Random random = new Random(); // once!
byte[] data = new byte[8388608];
for (int i = 0; i < data.length; i++) {
    data[i] = (byte) chars[random.nextInt(chars.length)];
}
上面的

会创建一次没有副本的数据。另请注意new Random() 8388608?循环中的时间也会导致大量内存使用,但它们应该很快收集垃圾。

然后你做

    public SlidingBubbles(int param1, int param2, byte[] data) {
        ...
        synchronized (mSignal) {
            tempBytesWritten = fc.write(ByteBuffer.wrap(data));

您不再创建该数据的副本,ByteBuffer.wrap不会创建数据副本。无论您做什么,都将完成的byte[]传递给SlidingBubbles

P.s:while(!executor.isTerminated()) {是错误的方法,有一种方法:How to wait for all threads to finish, using ExecutorService?

答案 1 :(得分:0)

while循环中的ByteBuffer.wrap(randomByteString.getBytes())。这是你的罪魁祸首。缓冲区驻留在内存中。使用后应该删除缓冲区。由于您正在重用此缓冲区,因此将其创建移出循环。 编辑: 试试这个,保持数组部分不变

private static final byte [] arr = randomByteString.getBytes();
for (int i = 0; i < 2; i++) {
        Runnable worker = new SlidingBubbles(param1, param2, arr)
        executor.execute(worker);
    }
在你的runnable中

尝试这个

try {

while(param1> 0) {
// Instantiate the 1st bubble file
file = new File(TARGET_DIR, String.valueOf(Calendar.getInstance().getTimeInMillis()));

while(param2 > 0) {                        
randomAccessFile = new RandomAccessFile(file, "rwd");
fc = randomAccessFile.getChannel();
fc.position(fc.size());
synchronized (mSignal) {
tempBytesWritten = fc.write(ByteBuffer.wrap(arr));
totalBytesWritten += tempBytesWritten;
}
// some other things that don't matter
}

答案 2 :(得分:0)

我认为你已经离开了StringBuffer(由于主线程没有退出,sb仍然会耗尽内存)?

因此,在完成使用后,可以通过将其归零来节省一些内存。即

String randomByteString = sb.toString();
sb = null;

答案 3 :(得分:0)

如果将字符串分成8 X 1MB的数组,它可能会减轻您的内存问题。这样:

1)你的StringBuilder只需要1MB,因此占用更少的内存 - 你可以通过使用setLength()方法重置它来重用它,我认为这意味着最坏的情况只需要1MB

2)你的ByteBuffer只需要1MB而不是8MB

将String输出到文件时,可以循环遍历数组。

所以你仍然需要8MB的字符串,但除此之外你应该只需要2MB。

答案 4 :(得分:0)

我接受了@Dexter的答案,因为它足以解决OutOfMemoryError异常的问题。但正如我在评论中提到的那样,fc.write()操作在解决后仍然返回0(写入字节)。

这是我最终得到的结果(没有任何问题,除了性能问题,我正在努力寻找最好的曲调)。

    // Create random string
    StringBuilder sb = new StringBuilder();
    sb.ensureCapacity(8388608); // ensuring 8 MB is allocated on the heap for the StringBuilder object
    for (long i = 0; i < DATA_SIZE; i++) {
            char c = chars[new Random().nextInt(chars.length)];
            sb.append(c);
    }
    String output = sb.toString();
    sb = null;

    ByteBuffer randomByteStringBuffer = ByteBuffer.wrap(output.getBytes());

    ExecutorService executor = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 2; i++) {
        Runnable worker = new SlidingBubbles(context, countRuns, originalTotalAvailable, totalBytesWritten, originalBytesAvailable, bubbleSize, randomByteStringBuffer);
        executor.execute(worker);
    }
    // This will make the executor accept no new threads and finish all existing threads in the queue 
    executor.shutdown();

    // Wait until all threads are finish 
    while(!executor.isTerminated()) {
        // wait for bubble threads to finish working...
    }

和线程&#39;常规...

private class SlidingBubbles implements Runnable {
    private int param1, param2;
    private ByteBuffer randomByteStringBuffer;
    private final Object mSignal = new Object();
    private volatile long tempBytesWritten = 0;
    private volatile long totalBytesWritten = 0;


    public SlidingBubbles(int param1, int param2, ByteBuffer randomByteStringBuffer) {
        this.param1= param1;
        this.param2= param2;
        this.randomByteStringBuffer = randomByteStringBuffer;
    }

    private void doIt() {
        File file = null;
        RandomAccessFile randomAccessFile = null;
        FileChannel fc = null;

        try {

            while(countRuns > 0) {
                // Instantiate the 1st bubble file
                file = new File(TARGET_DIR, String.valueOf(Calendar.getInstance().getTimeInMillis()));                    
                while(originalBytesAvailable > 0) {

                    randomAccessFile = new RandomAccessFile(file, "rw");
                    fc = randomAccessFile.getChannel();

                    fc.position(fc.size());
                    synchronized (mSignal) {
                        tempBytesWritten = fc.write(randomByteStringBuffer);

                        /* This rewind() is what did the trick. fc.write() started working normally again and writing 8MB.*/
                        randomByteStringBuffer.rewind();

                        totalBytesWritten += tempBytesWritten;
                    }
                 // some other things that don't matter
                }
             // some other things that don't matter
            }
        } catch (IOEception ioe) {
               Log.d(LOG_TAG, ioe.getMessage());
          }
    }

    @Override
    public void run() {
        wipe();            
    }
}

希望这对将来的其他人也有所帮助。