Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JNI Attach/Detach thread memory management

I have a JNI Callback:

void callback(Data *data, char *callbackName){
    JNIEnv *env;
    jvm->AttachCurrentThread((void **)&env, NULL);
    /* start useful code*/

    /* end useful code */
    jvm->DetachCurrentThread();
}

When I run it like this (empty useful code), I get a memory leak. If I comment out the whole method, there is no leak. What is the correct way of attaching / detaching threads?

My application processes real-time sound data, so the threads responsible for data processing must be done as soon as possible in order to be ready for another batch. So for these callbacks, I create new threads. There are dozens or even hundreds of them each second, they attach themselves to JVM, call a callback function which repaints a graph, detach and die. Is this a correct way of doing this stuff? How to handle the leaking memory?

EDIT: typo

OK I have created a mimimal code needed:

package test;

public class Start
{
    public static void main(String[] args) throws InterruptedException{
        System.loadLibrary("Debug/JNITest");
        start();
    }

    public static native void start();
}

and

#include <jni.h>
#include <Windows.h>
#include "test_Start.h"

JavaVM *jvm;
DWORD WINAPI attach(__in  LPVOID lpParameter);

JNIEXPORT void JNICALL Java_test_Start_start(JNIEnv *env, jclass){
    env->GetJavaVM(&jvm);
    while(true){
        CreateThread(NULL, 0, &(attach), NULL, 0, NULL);
        Sleep(10);
    }
}


DWORD WINAPI attach(__in  LPVOID lpParameter){
    JNIEnv *env;
    jvm->AttachCurrentThread((void **)&env, NULL);
    jvm->DetachCurrentThread();
    return 0;
}

and when I run the VisualJM profiler, I get the usual sawtooth pattern, no leak there. Heap usage peaked at around 5MB. However, observing the process explorer indeed shows some weird behaviour: memory is slowly rising and rising, 4K a second for a minute or so and then suddenly all this allocated memory drops. These drops do not correspond with garbage collection (they occur less often and deallocate less memory than those saw-teeth in the profiler).

So my best bet is that it is some OS behaviour handling tens of thousands milisecond-lived threads. Does some guru have an explanation for this?

like image 854
Jakub Zaverka Avatar asked Mar 09 '12 23:03

Jakub Zaverka


2 Answers

Several points about calling back into Java from native code:

  • AttachCurrentThread should only be called if jvm->GetEnv() returns JNI_EDETACHED. It's usually a no-op if the thread is already attached, but you can save some overhead.
  • DetachCurrentThread should only be called if you called AttachCurrentThread.
  • Avoid the detach if you expect to be called on the same thread in the future.

Depending on your native code's threading behavior, you may want to avoid the detach and instead store references to all native threads for disposal on termination (if you even need to do that; you may be able to rely on application shutdown to clean up).

If you continually attach and detach native threads, the VM must continually associate (often the same) threads with Java objects. Some VMs may re-use threads, or temporarily cache mappings to improve performance, but you'll get better and more predictable behavior if you don't rely on the VM to do it for you.

like image 167
technomage Avatar answered Oct 17 '22 00:10

technomage


I figured the problem. It was dangling local references in the JNI code I didn't destroy. Every callback would create a new local reference, thus resulting in a memory leak. When I converted the local reference to global, so I could reuse it, the problem disappeared.

like image 42
Jakub Zaverka Avatar answered Oct 17 '22 02:10

Jakub Zaverka