Android JNI本机代码中的C++对象是否会调用垃圾回收?

xav*_*sjs 10 c++ java java-native-interface garbage-collection android-ndk

所以,我有一个概念性的问题.我一直在Android上使用JNI来处理低级音频"东西".我已经用C/C++完成了大量的音频编码,所以我认为这不是什么大问题.我决定在我的"原生"代码中使用C++(因为谁不喜欢OOP?).我遇到的问题似乎(对我来说)是一个奇怪的问题:当我在C++代码中创建一个处理音频的对象时,我从未将此对象传递给Java(也没有其他方式),调用此方法对象似乎经常调用垃圾收集.由于这是在音频回调中发生的,结果是口吃的音频,我得到频繁的消息:

WAIT_FOR_CONCURRENT_GC blocked 23ms
Run Code Online (Sandbox Code Playgroud)

但是,当我通过创建静态函数(而不是在memeber对象上调用成员方法)执行相同的操作时,应用程序的性能似乎很好,我不再看到上面的日志消息.

基本上,是否有任何理由调用静态函数应该比在本机代码中的成员对象上调用成员方法具有更好的性能? 更具体地说,是成员对象还是完全位于垃圾收集中涉及的JNI项目的本机代码内的有限范围变量?GC中涉及C++调用堆栈吗?在JNI编程方面,有没有人可以告诉我C++内存管理如何满足Java内存管理?也就是说,如果我没有在Java和C++之间传递数据,那么我编写C++代码的方式是否会影响Java内存管理(GC或其他方式)?

请允许我举一个例子.忍受我,因为它很长,如果你认为你有洞察力,欢迎你不要在这里阅读.

我有几个对象.一个负责创建音频引擎,初始化输出等.它被称为HelloAudioJNI(抱歉没有编译可编译的例子,但有很多代码).

class CHelloAudioJNI {

    ... omitted members ...

    //member object pointers
    COscillator *osc;
    CWaveShaper *waveShaper;

    ... etc ...

public:
    //some methods
    void init(float fs, int bufferSize, int channels);

    ... blah blah blah ...
Run Code Online (Sandbox Code Playgroud)

接下来我还有几节课.WaveShaper类看起来像这样:

class CWaveShaper : public CAudioFilter {
protected:
    double *coeffs;
    unsigned int order;//order
public:
    CWaveShaper(const double sampleRate, const unsigned int numChannels,
                double *coefficients, const unsigned int order);

    double processSample(double input, unsigned int channel);
    void reset();
};
Run Code Online (Sandbox Code Playgroud)

现在让我们不要担心CAudioFilter类,因为这个例子已经很长了.WaveShaper .cpp文件如下所示:

CWaveShaper::CWaveShaper(const double sampleRate,
                         const unsigned int numChannels,
                         double *coefficients,
                         const unsigned int numCoeffs) :
    CAudioFilter(sampleRate,numChannels), coeffs(coefficients), order(numCoeffs)
{}

double CWaveShaper::processSample(double input, unsigned int channel)
{
    double output = 0;
    double pow = input;

    //zeroth order polynomial:
    output = pow * coeffs[0];

    //each additional iteration
    for(int iteration = 1; iteration < order; iteration++){
        pow *= input;
        output += pow * coeffs[iteration];
    }

    return output;
}

void CWaveShaper::reset() {}
Run Code Online (Sandbox Code Playgroud)

然后是HelloAudioJNI.cpp.这是我们深入研究问题的地方.我在init函数中使用new来正确创建成员对象,因此:

void CHelloAudioJNI::init(float samplerate, int bufferSize, int channels)
{
    ... some omitted initialization code ...

        //wave shaper numero uno
    double coefficients[2] = {1.0/2.0, 3.0/2.0};
    waveShaper = new CWaveShaper(fs,outChannels,coefficients,2);

    ... some more omitted code ...
}
Run Code Online (Sandbox Code Playgroud)

好吧到目前为止一切似乎都很好 然后在音频回调中我们调用成员对象上的一些成员方法,如下所示:

void CHelloAudioJNI::processOutputBuffer()
{
    //compute audio using COscillator object
    for(int index = 0; index < outputBuffer.bufferLen; index++){
        for(int channel = 0; channel < outputBuffer.numChannels; channel++){
            double sample;

            //synthesize
            sample = osc->computeSample(channel);
            //wave-shape
            sample = waveShaper->processSample(sample,channel);

            //convert to FXP and save to output buffer
            short int outputSample = amplitude * sample * FLOAT_TO_SHORT;
            outputBuffer.buffer[interleaveIndex(index,channel)] = outputSample;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是产生频繁音频中断和大量垃圾收集消息的原因.但是,如果我将CWaveShaper :: processSample()函数复制到回调上方的HelloAudioJNI.cpp并直接调用它而不是成员函数:

sample = waveShape(sample, coeff, 2);
Run Code Online (Sandbox Code Playgroud)

然后我从我的Android设备中获得了漂亮的美妙音频,而且我没有得到关于垃圾收集的频繁消息.问题是,成员对象还是有限范围变量完全位于垃圾收集中涉及的JNI项目的本机代码中?GC中涉及C++调用堆栈吗?在JNI编程方面,有没有人可以告诉我C++内存管理如何满足Java内存管理?也就是说,如果我没有在Java和C++之间传递数据,那么我编写C++代码的方式是否会影响Java内存管理(GC或其他方式)?

fad*_*den 5

C++对象和Dalvik的垃圾收集之间没有任何关系.Dalvik对本机堆的内容没有兴趣,除了它自己的内部存储.从Java源创建的所有对象都存在于"托管"堆上,这是垃圾收集发生的地方.

Dalvik GC不检查本机堆栈; VM已知的每个线程都有一个单独的堆栈供解释器使用.

C++和托管对象相关的唯一方法是,如果您选择通过以某种方式配对对象来创建关系(例如,从C++构造函数创建新的托管对象,或从Java终结器中删除本机对象).

您可以使用DDMS/ADT的"分配跟踪器"功能查看托管堆上最近创建的对象,以及它们的分配位置.如果你在GC乱舞期间运行它,你应该能够分辨出造成它的原因.

此外,logcat消息显示进程和线程ID(来自命令行使用adb logcat -v threadtime),您应该检查以确保消息来自您的应用程序,并查看GC活动正在发生的线程.您可以在DDMS/ADT的"线程"选项卡中查看线程名称.


小智 4

CHelloAudioJNI::init(...)在 waveShaper 中存储指向堆栈变量 ( ) 的指针double coefficients[2]。当您waveShaper->coeffs在系数超出范围后进行访问时,会发生 BadThings(tm)。

在构造函数中创建数组的副本CWaveShaper(并且不要忘记在析构函数中删除它)。或者使用std::array.