【问题标题】:Do C++ objects in Android JNI native code invoke garbage collection?Android JNI 本机代码中的 C++ 对象是否调用垃圾收集?
【发布时间】:2013-05-21 21:40:21
【问题描述】:

所以,我有一个概念性问题。我一直在使用 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::processSample() 函数复制到回调上方的 HelloAudioJNI.cpp 并直接调用它而不是成员函数:

sample = waveShape(sample, coeff, 2);

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

【问题讨论】:

    标签: java c++ android-ndk garbage-collection java-native-interface


    【解决方案1】:

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

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

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

    您可以使用 DDMS / ADT 的“分配跟踪器”功能来查看托管堆上最近创建的对象,以及它们被分配的位置。如果你在 GC 乱跑期间运行它,你应该能够知道是什么原因造成的。

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 这可能是切线的,因为系数听起来像是可以读取但不会写入的东西。因此,访问时的值可能是不确定的,但尚不清楚是否会发生堆栈损坏。
      • @ChrisStratton,你能解释一下你的评论吗?我不明白你的意思。
      • 这里提出的问题是您应该解决的问题,因为从不再分配的内存访问的系数值可能不正确。但是,除非您稍后尝试更改系数,否则这不会导致程序流程不正确,这似乎是此答案隐含地提供的垃圾收集的唯一解释。如果您仅读取在对所有输入都有效的计算中使用的系数,则“BadThings(tm)”仅限于“错误的计算结果”
      • 好电话。我遇到了回调问题,并且 reinterpret_cast 不起作用。问题是我只在 JNI 函数中声明了我的回调上下文,没有全局引用,所以数据丢失了。在某种程度上类似于 GC:D
      猜你喜欢
      • 2016-12-05
      • 1970-01-01
      • 2012-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多