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

Do C++ objects in Android JNI native code invoke garbage collection?

提问人:xaviersjs 提问时间:5/22/2013 更新时间:5/29/2013 访问量:3791

问:

所以,我有一个概念性的问题。我一直在 Android 上使用 JNI,目的是做一些低级音频“东西”。我已经用 C/C++ 进行了大量的音频编码,所以我认为这不会是什么大问题。我决定在我的“原生”代码中使用 C++(因为谁不喜欢 OOP?)。我遇到的问题(对我来说)似乎是一个奇怪的问题:当我在 C++ 代码中创建用于处理音频的对象时,我从未将此对象传递给 Java(反之亦然),在此对象上调用方法似乎经常调用垃圾回收。由于这发生在音频回调中,因此结果是音频断断续续,并且我经常收到以下消息:

WAIT_FOR_CONCURRENT_GC blocked 23ms

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

基本上,在本机代码中,调用静态函数应该比在成员对象上调用成员方法具有更好的性能吗?更具体地说,成员对象或完全位于 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 ...

因此,我还有几节课。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();
};

现在我们不要担心 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() {}

然后是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 ...
}

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

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

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

sample = waveShape(sample, coeff, 2);

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

java c++ android-ndk 垃圾回收 java-native-interface

评论


答:

4赞 youdontneedtothankme 5/22/2013 #1

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

在构造函数中复制数组(不要忘记在析构函数中删除它)。或使用 .CWaveShaperstd::array

评论

0赞 Chris Stratton 5/22/2013
这可能是切线的,因为系数听起来像是可以读取但不能写入的东西。因此,访问时的值可能是不确定的,但目前尚不清楚堆栈损坏是否会发生。
0赞 xaviersjs 5/22/2013
@ChrisStratton,你能解释一下你的评论吗?我不明白你的意思。
0赞 Chris Stratton 5/22/2013
这里提出的问题是您应该解决的问题,因为从不再分配的内存访问的系数值可能不正确。但是,除非您稍后尝试更改系数,否则这不会导致不正确的程序流,这似乎是此答案隐含的垃圾回收的唯一解释。如果您只读取对所有输入都有效的计算中使用的系数,则“BadThings(tm)”仅限于“错误的计算结果”
0赞 Michael Ribbons 3/16/2019
好电话。我遇到了回调问题,reinterpret_cast不起作用。问题是我只在 JNI 函数中声明了我的回调上下文,没有全局引用,因此数据丢失了。在某种程度上与GC相似:D
5赞 fadden 5/29/2013 #2

C++ 对象和 Dalvik 的垃圾回收之间没有关系。Dalvik 对本机堆的内容不感兴趣,除了对自己的内部存储感兴趣。从 Java 源代码创建的所有对象都位于“托管”堆上,这是进行垃圾回收的地方。

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

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

您可以使用 DDMS/ADT 的“分配跟踪器”功能查看托管堆上最近创建的对象,以及从何处分配这些对象。如果您在 GC 乱舞期间运行它,您应该能够分辨出导致它的原因。

此外,logcat 消息还会显示进程和线程 ID(从命令行使用,),您应该检查这些 ID 以确保消息来自您的应用程序,并查看 GC 活动发生在哪个线程上。您可以在 DDMS/ADT 的“线程”选项卡中看到线程名称。adb logcat -v threadtime