我正在开发一个需要与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)上增加这个堆空间是不可能的(或者至少似乎是,根据我的理解)。
有什么我想念的吗? 非常感谢任何帮助。
答案 0 :(得分:1)
您可以静态获取8MB数据,但永远不会创建它的副本。在Android上,StringBuilder
与char[]
共享内部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();
}
}
希望这对将来的其他人也有所帮助。