在我的Android应用程序中,我在线程中有一个循环,该循环对存储在DoubleBuffer对象中的数据进行了一些处理。令我惊讶的是, put(DoubleBuffer) 似乎正在分配内存!

这是我应该期望的吗?对我来说,这似乎与我以为我对NIO缓冲区了解的一切背道而驰。有什么我应该避免的事情吗?

我可以用一个最小的示例来重现这一点,该示例只需在循环中的两个缓冲区之间复制即可。在运行Android 4.4.2的物理电话上通过Android Studio运行时,日志中填充了以下几行:

D/dalvikvm: GC_FOR_ALLOC freed 2032K, 55% free 6556K/14500K, paused 11ms, total 11ms

如果我注释掉对put()的调用,则不会发生。

这是我复制此问题的代码,写在“空 Activity ”模板上:
public class MainActivity extends AppCompatActivity {
    private Thread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                DoubleBuffer buffer1 = DoubleBuffer.allocate(5000);
                DoubleBuffer buffer2 = DoubleBuffer.allocate(buffer1.capacity());
                while (!Thread.interrupted()) {
                    // set remaining to capacity so that entire buffer is copied
                    buffer1.clear();
                    buffer2.clear();
                    buffer2.put(buffer1);
                    Thread.yield();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    @Override
    protected void onStop() {
        if (thread != null) {
            thread.interrupt();
            thread = null;
        }
        super.onStop();
    }
}

这是app/build.gradle的相关部分:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 25
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:25.1.0'
}

最佳答案

浏览AOSP源代码,这似乎恰好在发生。 4.4.2中的DoubleBuffer.put(DoubleBuffer)的相关代码是:

double[] doubles = new double[src.remaining()];
src.get(doubles);
put(doubles);

因此,他们要支付堆分配的费用,以便他们可以从System.arraycopy()中受益。相同的代码用于DoubleBufferByteBuffer视图(无论是否直接)。

据我所知,这段代码至少可以追溯到2.3。从Android 7.0开始,它已被替换为:
int n = src.remaining();
if (n > remaining())
    throw new BufferOverflowException();
for (int i = 0; i < n; i++)
    put(src.get());

因此,如果您希望避免在所有Android版本之间以紧密的循环分配内存,并且确实要(或必须)使用缓冲区,则需要自己实现put(DoubleBuffer)功能。例如:
while (fromBuffer.hasRemaining()) {
    toBuffer.put(fromBuffer.get());
}

或者,如果要从支持数组的缓冲区(!fromBuffer.isDirect())复制:
toBuffer.put(fromBuffer.array(), fromBuffer.position(), fromBuffer.remaining());

所有非字节缓冲区(IntBufferLongBufferShortBufferFloatBufferCharBuffer)的情况都相同。

07-24 22:10