JNI ReferenceTable overflow: how to release ObjectArray containing Strings - java

I am having a frustrating time resolving this issue and the JNI documentation is woefully sparse. Assistance would be greatly appreciated!
Am assigned a project on an Android networking app. The native side stores information about the users you are connected to and I need to send this user list to the Java side to update UI info. Essentially I need to compose a string array and a boolean array which contain the names and flags of connected users, but later more info may be sent. My difficulty is in releasing & cleaning up the Object array containing the String information. I am not clear on how to do this. Here is what I have:
void name_list_cb(struct user_info* user_list, size_t count, void *userdata)
{
jobject callbacks = (jobject)userdata;
JNIEnv *env;
(*g_vm)->GetEnv(g_vm, (void**)&env, JNI_VERSION_1_4);
jclass cls = (*env)->GetObjectClass(env, callbacks);
jmethodID method = (*env)->GetMethodID(env, cls, "user_list", "([Ljava/lang/String;[Z)V");
int i;
jobjectArray name_list;
jbooleanArray connected_list;
name_list = (jobjectArray)(*env)->NewObjectArray(env, count, (*env)->FindClass(env, "java/lang/String"), (*env)->NewStringUTF(env, ""));
connected_list = (jbooleanArray)(*env)->NewBooleanArray(env, count);
uint8_t boolean_arr[count];
for(i = 0; i < count; i++) {
(*env)->SetObjectArrayElement(env, name_list, i, (*env)->NewStringUTF(env, user_list[i].name));
boolean_arr[i] = user_list[i].connected;
}
(*env)->SetBooleanArrayRegion(env, connected_list, 0, count, (jboolean *)boolean_arr);
(*env)->CallVoidMethod(env, callbacks, method, name_list, connected_list);
(*env)->ReleaseBooleanArrayElements(env, connected_list, (jboolean *)boolean_arr, 0);
for(i = 0; i < count; i++) {
(*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, name_list, i), user_list[i].name);
//(*env)->ReleaseObjectArrayElements(env, name_list, count, 0);
}
(*env)->DeleteLocalRef(env, boolean_arr);
(*env)->DeleteLocalRef(env, name_list);
(*env)->DeleteLocalRef(env, connected_list);
(*env)->DeleteLocalRef(env, cls);
}
I get either a "referencetable overflow", or a "signal 11 (SIGSEGV), fault addr deadbaad". The overflow/memleak is the main prob. Basically I am not releasing the UTFChars and the Object elements. Although I have seen references to it online, my JNI version does not have ReleaseObjectArrayElement[s]. I have been researching how to do this exactly but no luck so far!

I think the problem is (*env)->DeleteLocalRef(env, boolean_arr); because boolean_arr is C function stack variable.

typedef jarray jobjectArray;
void (*DeleteLocalRef)(JNIEnv*, jobject);
So I think use DeleteLocalRef could release the jobjectArray.
Or you can just new the jobjectArray just one time in an initialize method.

Related

change Byte[] values in JNI with android studio

i want to reference a parameter, Byte[], in a JNI function and replace values of them.
The declaration of JNI is below.
public native void imageprocessing(long inputImage, long inputImage2, long outputImage, long outputImage2, Byte[] sim);
The sim is the target what i want to change.
The interface of it is below.
Java_com_example_duru_opencvtest_MainActivity_imageprocessing(JNIEnv *env, jobject instance,
jlong inputImage, jlong inputImage2, jlong outputImage, jlong outputImage2, jobjectArray sim)
it uses jobjectArray type and i want to put int type values of native language into sim object.
so i my method is
jbyteArray byte_array = env->NewByteArray(4);
env->SetByteArrayRegion(byte_array, 0, 4, (jbyte*)tempSim);
jobjectArray object_array = env->NewObjectArray(4, env->FindClass("java/lang/Byte"), byte_array);
/* ERROR
(*env).SetObjectArrayElement(sim, 0, (jobject)object_array[0]);
(*env).SetObjectArrayElement(sim, 1, (jobject)object_array[1]);
(*env).SetObjectArrayElement(sim, 2, (jobject)object_array[2]);
(*env).SetObjectArrayElement(sim, 3, (jobject)object_array[3]);
*/
tempSim is 'int tempSim[4]' and Sim also has 4 length.
(*env).SetObjectArrayElement(sim, 0, (jobject)object_array[0]);
The bold part occur syntax error than the other part have no problem?
jclass javaLangByteClass = env->FindClass("java/lang/Byte");
jmethodID javaLangByteConstructor = env->GetMethodID(javaLangByteClass , "<init>", "(B)Ljava/lang/Byte;")
for (int i=0; i<3; i++) {
jobject nextElement = env->NewObject(javaLangByteClass, javaLangByteConstructor, (jbyte)tempSim[i]);
env->SetObjectArrayElement(sim, i, nextElement);
env->DeleteLocalRef(nextElement);
}
See the comment below: Byte.valueOf() may be more efficient than the constructor:
jclass javaLangByteClass = env->FindClass("java/lang/Byte");
jmethodID javaLangByteStaticValueOf = env->GetStaticMethodID(javaLangByteClass , "valueOf", "(B)Ljava/lang/Byte;")
for (int i=0; i<3; i++) {
jobject nextElement = env->CallStaticObjectMethod(javaLangByteClass, javaLangByteStaticValueOf, (jbyte)tempSim[i]);
env->SetObjectArrayElement(sim, i, nextElement);
env->DeleteLocalRef(nextElement);
}

Pass Array of Mat to NDK

I followed same steps of taking native address in array and passed to NDK.Arraylist pass java to ndk
And in the native side i done as given below,
JNIEXPORT jint JNICALL Java_com_app_android_flowerhgram_1woutcondition_TemplateMatch_MatchTemplate
(JNIEnv *env, jclass, jlong addrProcessed, jlongArray templates, jobject out){
Mat& mProcesseed = *(Mat *)addrProcessed;
vector <Mat> trainimgs;
jint retVal=0;
int num=0,temp=0;
jclass alCls = env->FindClass("org/opencv/core/Mat");
jmethodID jMatCons = env->GetMethodID(alCls,"<init>","()V");
jmethodID alGetId = env->GetMethodID(alCls, "getNativeObjAddr", "()J");
jmethodID sizeMethodID = env->GetMethodID(alCls, "size", "()I");
jlong *traindata = env->GetLongArrayElements(templates,0);
int intValue = *(int*) sizeMethodID;
for(int k=0;k < intValue ; k++)
{
Mat & newimage=*(Mat*)traindata[k];
trainimgs.push_back(newimage);
}
env->ReleaseLongArrayElements(templates,traindata,0);
return retVal;
}
For this i am not facing any build error, but while compiling i getting error in size
Pending exception java.lang.NoSuchMethodError: no non-static method "Lorg/opencv/core/Mat;.size()I".
please guide me for this or any other solution will be appreciated.
After long study, i found the solution for this question. Hope this may useful for others
To get length of array the below line can be used,
jsize a_len = env->GetArrayLength(templates);
And a_len can be used in for loop
This solved my problem for my above question.

Returning an arraylist of string from Native java to JNI

ArrayList<String> myArraylist;
public ArrayList<String> getData(){
myArraylist = new ArrayList<String>();
myArraylist.add("1267982563");
myArraylist.add("2345678");
myArraylist.add("5432789");
return myArraylist;
}
How to get the each items from the above method in JNI side and Push to vector and return from the JNI to other CPP calls in the JNI layer.
Convert ArrayList to std::vector<std::string>:
jclass java_util_ArrayList;
jmethodID java_util_ArrayList_;
jmethodID java_util_ArrayList_size;
jmethodID java_util_ArrayList_get;
jmethodID java_util_ArrayList_add;
thread_local JNIEnv *env;
void init() {
java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
java_util_ArrayList_size = env->GetMethodID (java_util_ArrayList, "size", "()I");
java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
}
std::vector<std::string> java2cpp(jobject arrayList) {
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i=0; i<len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char *pchars = env->GetStringUTFChars(element, nullptr);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
}
Push ArrayList from JNI back to Java
If you don't modify this list in JNI, then the best strategy would be to simply keep a global reference to it somewhere in your native code. If you modify it a little, keep this jobject always up-to-date (you will probably need the methods java_util_ArrayList_add or java_util_ArrayList_set).
If you choose to reconstruct the list from scratch the vector, you will unwind the above method:
jobject cpp2java(std::vector<std::string> vector) {
jobject result = env->NewObject(java_util_ArrayList, java_util_ArrayList_, vector.size());
for (std::string s: vector) {
jstring element = env->NewStringUTF(s.c_str());
env->CallBooleanMethod(result, java_util_ArrayList_add, element);
env->DeleteLocalRef(element);
}
return result;
}
At any rate, be careful with threads when you work with Jni, always attach your native threads and detach before the native thread is destroyed.

SIGSEGV when calling Java method from native pthread

In a Java project that uses C code via JNI I have a piece of native C code that obtains references to an object and one of its methods, then starts a native thread, passing these references to it in a struct. When the thread tries to call the method, the code crashes with a SIGSEGV. Calling that same method from the main thread works.
Doing some research I learned that the env reference is only valid within the thread and that any other native thread must be attached first. I did this but the code still crashes on the first call to the method.
Strangely, when I call the same method from the main thread before I create the other thread (just uncomment the line in the main code), things work for some time. The output thread loops for some 10,000 times before it crashes.
The method call is to DataOutputStream.writeShort(). The thread in question is the only one writing to the DataOutputStream. However, the DataOutputStream is connected to a DataInputStream.
Simplified native code:
static void write_output(struct output_state *s) {
int i;
jint sample;
for (i = 0; i < 2 * s->result_len; i += 2) {
sample = (s->result[i] << 8) + s->result[i+1];
(*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
}
}
static void *output_thread_fn(void *arg)
{
struct output_state *s = arg;
(*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
while (!do_exit) {
// use timedwait and pad out under runs
safe_cond_wait(&s->ready, &s->ready_m);
pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
write_output(s);
pthread_rwlock_unlock(&s->rw);
}
(*(s->jvm))->DetachCurrentThread(s->jvm);
return 0;
}
JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
(JNIEnv *env, jobject self) {
jclass clsSelf = (*env)->GetObjectClass(env, self);
jfieldID fTunerOut = (*env)->GetFieldID(env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
jobject tunerOut = (*env)->GetObjectField(env, self, fTunerOut);
jclass cls = (*env)->GetObjectClass(env, tunerOut);
jmethodID writeShortID = (*env)->GetMethodID(env, cls, "writeShort", "(I)V");
if (!writeShortID || !cls)
return 0;
(*env)->GetJavaVM(env, &(output.jvm));
output.tunerOut = tunerOut;
output.writeShort = writeShortID;
// (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing
pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
usleep(100000);
pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
return 1;
}
Are JNI references such as jclass, jfieldID, jobject and jmethodID subject to the same limitations as JNIEnv, i.e. valid only within the same thread?
Suspecting this, I moved the JNI reference stuff from open() to output_thread(), right after the call to AttachCurrentThread(). However, I still need to pass a jobject reference (self) across thread borders, and the call to GetObjectClass() crashes.
What is the correct way to create a thread native code and have that thread call a particular method of a given class instance?
Turns out my suspicion was correct: jobject and jclass references are indeed local, i.e. valid only within the same thread and only until the current native method returns. See http://developer.android.com/training/articles/perf-jni.html#local_and_global_references.
My approach of moving the reference-related code to the thread function was correct, except that I need to first convert self into a global reference by calling NewGlobalRef().
Updated code:
static void write_output(struct output_state *s) {
int i;
jint sample;
for (i = 0; i < 2 * s->result_len; i += 2) {
sample = (s->result[i] << 8) + s->result[i+1];
(*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
}
}
static void *output_thread_fn(void *arg)
{
struct output_state *s = arg;
(*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
jclass clsSelf = (*(s->env))->GetObjectClass(s->env, s->self);
jfieldID fTunerOut = (*(s->env))->GetFieldID(s->env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
s->tunerOut = (*(s->env))->GetObjectField(s->env, s->self, fTunerOut);
jclass cls = (*(s->env))->GetObjectClass(s->env, s->tunerOut);
s->writeShort = (*(s->env))->GetMethodID(s->env, cls, "writeShort", "(I)V");
while (!do_exit) {
// use timedwait and pad out under runs
safe_cond_wait(&s->ready, &s->ready_m);
pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
write_output(s);
pthread_rwlock_unlock(&s->rw);
}
(*(s->jvm))->DetachCurrentThread(s->jvm);
return 0;
}
JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
(JNIEnv *env, jobject self) {
jclass clsSelf = (*env)->GetObjectClass(env, self);
if (!writeShortID || !cls)
return 0;
output.self = (*env)->NewGlobalRef(env, self);
(*env)->GetJavaVM(env, &(output.jvm));
(*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing
pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
usleep(100000);
pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
return 1;
}
One thing still missing is a call to DeleteGlobalRef() when the output thread is done. This is to make sure the global reference is released when it is no longer needed, so that the garbage collector can pick it up.

Memory leak in SWIG polymorphism across C++ and Java using directors

I have a C++ program to receive binary data from network. After data is received, it will callback the data as a byte array from C++ to a Java client. I use the director feature in SWIG to easily achieve cross language polymorphism across C++ and Java for callback. Here's a link.
But I found there is no typemaps in SWIG from char* in C++ to byte[] in Java. So, I added a patch to the virous.i. Here's a link.
/*Director specific typemaps*/
%typemap(directorin, descriptor="[B") char *BYTE {
jbyteArray jb = (jenv)->NewByteArray(strlen(BYTE));
(jenv)->SetByteArrayRegion(jb, 0, strlen(BYTE), (jbyte*)BYTE);
$input = jb;
}
%typemap(directorout) char *BYTE {
$1 = 0;
if($input){
$result = (char *) jenv->GetByteArrayElements($input, 0);
if(!$1)
return $null;
jenv->ReleaseByteArrayElements($input, $result, 0);
}
}
%typemap(javadirectorin) char *BYTE "$jniinput"
%typemap(javadirectorout) char *BYTE "$javacall"
For example. I have a class in C++ called A:
class A{
public:
virtual void onDataReceived(const char* BYTE, const size_t len) {}
};
Then in Java code I have another class B to extend A:
class B extends A {
#Override
public void onDataReceived(byte[] data, long len) {
//handling the data.
}
}
I can receive the byte array in Java code, but it seems it is never garbage collected by JVM. The onDataReceived method in SWIG generated wrapper file is like this:
void SwigDirector_A::onDataReceived(char const *BYTE, size_t const len) {
JNIEnvWrapper swigjnienv(this);
JNIEnv * jenv = swigjnienv.getJNIEnv();
jobject swigjobj = (jobject) NULL;
jbyteArray jBYTE = 0;
jlong jlen;
if (!swig_override[3]) {
A::onDataReceived(BYTE,len);
return;
}
swigjobj = swig_get_self(jenv);
if (swigjobj && jenv->IsSameObject(swigjobj, NULL) == JNI_FALSE) {
{
jbyteArray jb = (jenv)->NewByteArray(strlen(BYTE));
(jenv)->SetByteArrayRegion(jb, 0, strlen(BYTE), (jbyte*)BYTE);
jBYTE = jb;
}
jlen = (jlong) len;
jenv->CallStaticVoidMethod(Swig::jclass_DataTransferJNI, Swig::director_methids[3], swigjobj, jBYTE, jlen);
if (jenv->ExceptionCheck() == JNI_TRUE) return;
} else {
SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null upcall object");
}
if (swigjobj) jenv->DeleteLocalRef(swigjobj);
}
In my C++ code, I will delete the data in the receive buffer after the callback is done. But I checked from Java VisualVM that the memory used by java client process is not GC-ed after a long time. Thanks advance for any help!
PS. The data is quite large, it is around 32KB.
I got it solved by added deletion of reference to jb in Director typemaps: (jenv)->DeleteLocalRef(jb);
Here's the updated patch.

Categories