Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

So, I've got a conceptual question. I've been working with JNI on Android for the purposes of doing low-level audio "stuff." I've done plenty of audio coding in C/C++, so I figured that this would not be much of a problem. I decided to do use C++ in my "native" code (because who doesn't love OOP?). The issue I've encountered seems (to me) to be a strange one: when I create an object for processing audio in the C++ code, and I never pass this object to Java (nor the other way around), calling methods on this object seems to invoke the garbage collection quite often. Since this is happening inside audio callbacks, the result is stuttering audio, and I get frequent messages along the lines of:

WAIT_FOR_CONCURRENT_GC blocked 23ms

However, when I perform the same operations by creating static functions (rather than invoking member methods on a memeber object) the performance of the app seems to be fine, and I no longer see the above log message.

Basically, is there any reason calling a static function should have better performance than calling member methods on a member object in native code? More specifically, are member objects, or limited scope variables that live entirely inside the native code of a JNI project involved in garbage collection? Is the C++ call stack involved in GC? Is there any insight anybody can give me on how C++ memory management meets Java memory management when it comes to JNI programming? That is, in the case that I'm not passing data between Java and C++, does the way I write C++ code affect Java memory management (GC or otherwise)?

Allow me to try to give an example. Bear with me, 'cause it's freaking long, and if you think you have insight you're welcome to stop reading here.

I have a couple of objects. One that is responsible for creating the audio engine, initializing output, etc. It is called HelloAudioJNI (sorry for not putting compile-able examples, but there's a lot of code).

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 ...

It follows that I have a couple more classes. The WaveShaper class looks like this:

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

Let's not worry about the CAudioFilter class for now, since this example is already quite long. The WaveShaper .cpp file looks like this:

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() {}

and then there's HelloAudioJNI.cpp. This is where we get into the meat of the issue. I create the member objects properly, using new inside the init function, thusly:

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

Ok everything seems fine so far. Then inside the audio callback we call some member methods on the member object like so:

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

This is what produces frequent audio interruptions and lots of messages about garbage collection. However, if I copy the CWaveShaper::processSample() function to the HelloAudioJNI.cpp immediately above the callback and call it directly instead of the member function:

sample = waveShape(sample, coeff, 2);

Then I get beautiful beautiful audio coming out of my android device and I do not get such frequent messages about garbage collection. Once again the questions are, are member objects, or limited scope variables that live entirely inside the native code of a JNI project involved in garbage collection? Is the C++ call stack involved in GC? Is there any insight anybody can give me on how C++ memory management meets Java memory management when it comes to JNI programming? That is, in the case that I'm not passing data between Java and C++, does the way I write C++ code affect Java memory management (GC or otherwise)?

like image 804
xaviersjs Avatar asked May 21 '13 21:05

xaviersjs


People also ask

Does C use garbage collection?

C does not have automatic garbage collection. If you lose track of an object, you have what is known as a 'memory leak'. The memory will still be allocated to the program as a whole, but nothing will be able to use it if you've lost the last pointer to it. Memory resource management is a key requirement on C programs.

How does JNI work on Android?

JNI is the Java Native Interface. It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++).

How does Android APK execute compiled native code using the Java native interface?

Using Android Studio 2.2 and higher, you can use the NDK to compile C and C++ code into a native library and package it into your APK using Gradle, the IDE's integrated build system. Your Java code can then call functions in your native library through the Java Native Interface (JNI) framework.

What is garbage collector Android?

Garbage collection Once it determines that a piece of memory is no longer being used by the program, it frees it back to the heap, without any intervention from the programmer. The mechanism for reclaiming unused memory within a managed memory environment is known as garbage collection.


1 Answers

There is no relationship between C++ objects and Dalvik's garbage collection. Dalvik has no interest in the contents of the native heap, other than for its own internal storage. All objects created from Java sources live on the "managed" heap, which is where garbage collection takes place.

The Dalvik GC does not examine the native stack; each thread known to the VM has a separate stack for the interpreter to use.

The only way C++ and managed objects are related is if you choose to create a relationship by pairing objects in some way (e.g. creating a new managed object from a C++ constructor, or deleting a native object from a Java finalizer).

You can use the "Allocation Tracker" feature of DDMS / ADT to see the most-recently created objects on the managed heap, and from where they are being allocated. If you run that during the GC flurry you should be able to tell what's causing it.

Also, the logcat messages show the process and thread IDs (from the command line use, adb logcat -v threadtime), which you should check to make sure that the messages are coming from your app, and also to see which thread the GC activity is occurring on. You can see the thread names in the "Threads" tab in DDMS / ADT.

like image 128
fadden Avatar answered Sep 28 '22 05:09

fadden