简体   繁体   中英

OutOfMemory Exception when multithreading … heap space?

I am working on an application that needs to be compatible with Android 2.3 (Gingerbread), and the device I'm using for development tests is a Motorola Atrix MB860 running Android 2.3.6.

In this device I get roughly 40MB of maximum heap space and as far as I could realize, my app uses around 33MB, but I get an OutOfMemoryError exception anyway.

Basically, the part of my code that matters to this issue creates a large String (8MB - I know it's rather big, but if it's too small it won't satisfy one of the requirements) and then goes on to create 2 threads that use such string to write to a certain memory space concurrently.

Here is the code:

    // 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...
    }

and the threads' routine:

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

Awkwardly (to me), on the 30th line of the thread routine ( tempBytesWritten = fc.write(ByteBuffer.wrap(randomByteString.getBytes())); ), the 2nd thread ("pool-1-thread-2") launches the exception, exits, and the 1st thread ("pool-1-thread-1") continues (actually, starts) executing normally. By the time the JVM is done allocating space for that large String , the app is using 33MB of the heap. As you can see from the code, that String is created only once, but then used multiple times from both threads.

Shouldn't the threads be using just a reference to the String rather than copying it? (which would, in this case, exceed the 40MB allowance).

I must also point out that it is (or at least seems to be, as far as my understanding goes) impossible to increase this heap space on Gingerbread ( previous research ).

Is there anything I'm missing? Any help is greatly appreciated.

You can have the 8MB data once statically and never create copies of it. On Android, StringBuilder shares the internal char[] with String but String#getBytes() creates a copy of the data each time.

I assume your characters are plain ASCII, this doesn't work correctly when they are more special.

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

above would create the data once without copies. Also note that new Random() 8388608? times in a loop will also cause massive memory usage, they should quickly get garbage collected though.

When you then do

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

You're no longer creating copies of that data, ByteBuffer.wrap does not create a copy of the data. Whatever you do, pass the finished byte[] to SlidingBubbles .

Ps: while(!executor.isTerminated()) { is the wrong way, there is a method for that: How to wait for all threads to finish, using ExecutorService?

ByteBuffer.wrap(randomByteString.getBytes()) inside a while loop. This is your culprit. Buffers reside in memory. You should get rid of a buffer after using it. Since you are reusing this buffer, move its creation out of the loop. EDIT : try this, keep the array part constant

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

in your runnable try this

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
}

I think you have left the StringBuffer hanging around too (since the main thread doesn't exit, sb will still be using up memory)?

So you could save some memory by nulling it out after you've finished using it. Ie

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

It might relieve your memory problems if you break your string up into an array of 8 X 1MB chunks. This way:

1) Your StringBuilder only needs to be 1MB so uses up less memory - you could reuse it by resetting it with the setLength() method which I think will mean worst case it will only take up 1MB

2) Your ByteBuffer only needs to be 1MB not 8MB

You can just loop round your array when you write the String out to files.

So you'll still need the 8MB for the string, but apart from that you should only need 2MB extra.

I accepted @Dexter's answer because it was enough to solve my problem with the OutOfMemoryError exception. But as I mentioned in the comments, fc.write() operations were still returning 0 (bytes written) after that had been solved.

Here is what I ended up with (no issues, except performatic ones, to which I'm working around to find the best tune).

    // 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...
    }

and the threads' routine...

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

Hopefully this will be helpful to someone else as well in the future.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM