JNI, Mulithreading and calling methods - java

I'm trying to do the following (not sure if I'm missing something or if this is not possible):
I have a Java class (in this particular case a Servlet) that calls a native method.
In this native method I'm spawning a new thread, and in that thread I would like to call a method on that Java object. Is that even possible?
What I tried in the native method that is called (original thread) is to remember the JavaVM instance, so that I can later can attach the other thread to it (seems to work), and make a NewGlobal ref for the jobject:
JavaVM *vm;
env->GetJavaVM(&vm);
env->NewGlobalRef(jobj)
What I don't know is how to retrieve the jobject in the other thread. If I just pass it the VM crashes, I assume because of an illegal thread access. If I create a new object for the class, I wouldn't have the exact object that I need.
Any ideas?
Thank you,
Mark
SOME ADDITIONAL CODE (method names etc. obscured):
The method that is called from the servlet:
JNIEXPORT jstring JNICALL ABC (JNIEnv *env, jobject jobj, some more arguments
{
JavaVM *vm;
env->GetJavaVM(&vm);
env->NewGlobalRef(jobj);
// spawning new thread (I'm using boost libraries)
boost::thread t = boost::thread(boost::bind(&XYZ::DEF, instance of XYZ, vm, &jobj);
...
}
void XYZ::DEF(JavaVM* vm, jobject* jobj)
{
JNIEnv* env;
vm->GetEnv( (void**)&env, JNI_VERSION_1_2);
vm->AttachCurrentThread((void**)&env, NULL);
... then eventually calling another method, but still the same thread, where I'm doing this:
jobject bb = env->NewDirectByteBuffer(...,...); // this crashed when I just used the env from the original thread, seems to work since I'm attaching the second thread to the VM.
// it crashes somewhere during the following code:
jclass cls = env->GetObjectClass(jobj);
jmethodID mid = env->GetMethodID(cls, "callback", "(I)V");
env->CallVoidMethod(jobj, mid, 13);
The crash produces something like this "A fatal error has been detected by the JRE... Problematic frame: V [libjvm.so+0x3e9220]...

You seem to be ignoring the result of NewGlobalRef. You have to use its result in the other thread instead of the original jobj.

Related

Does callback copy jnienv, jinstance inside a JNI function?

The lambda that I pass to builder is populated into className object, and called at regular intervals (every hour) of time to refresh the other members. It gets called the first time successfully. I'm not sure if the lambda retains env, instance to legally call the reverse JNI function?
JNIEXPORT jint JNICALL
Java_com_company_app_ClassName_JniInit(JNIEnv *env, jobject instance){
int data = 0;
auto builder = new Builder(data,
[env, instance]() -> std::string {
std::string stringObj = populateData(env, instance); // This function makes a reverse JNI call to get data from a java function in the class
return stringObj;
}
);
std::shared_ptr<className> = builder->build();
return 1;
}
I seem to be getting a SIGNAL 11 error, SIGSEGV. Is this kind of segmentation fault catchable in any way, so the app doesn't crash?
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x228 in tid 21785 (ClassName), pid 21573 (.company.app)
It seems to be crashing at this line inside populateData-
jstring data = (jstring)(env)->CallObjectMethod(instance, javaFunctionName);
Is there a way to check if this function will fail before calling it? I checked if env (JNIEnv* argument in populateData) is NULL, but its not, and has a valid address along with instance (jinstance argument in populateData).
You'll have problems with jobject instance if this function does something asynchronously. The reason is that before this function is started, Java marks the object as having an extra reference. It removes that when it returns. So after it returns, the object can be cleaned up by the garbage collector if there's no other instances in the Java code.
This can be fixed by calling NewGlobalRef(JNIEnv *env, jobject obj) before starting the async function on the main thread, and calling DeleteGlobalRef at the end of the callback when jobject is no longer needed.
To answer this question, I have found a slightly different kind of hack.
Don't copy the JNIEnv, and object or create references to them. They get deleted as soon as your JNI function goes out of scope. I'm not sure why copying doesn't work (if someone could answer this, that would be great). Alternatively, I've used JavaVM* to maintain a local reference of the jvm, you can do this in your JNI_OnLoad.
Use the function below, and it would work fine.
JavaVM* jvm; // Initialise this in OnLoad
JNIEnv *getJNIEnv()
{
JNIEnv *env = nullptr;
jint ret = jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret == JNI_EDETACHED || !env)
{
env = nullptr;
}
return env;
}
If you need the class instance, you'll need to initialise it via JNIEnv, but I use a static method for my class, so I just fetched the class using env->FindClass and then you can do env->GetStaticMethodID along with env->CallStaticObjectMethod.

What causes the JNI error "use of deleted local reference"?

I have an Android app where the following C method is called when the app starts (in Activity.onCreate).
extern "C"
JNIEXPORT jstring JNICALL
Java_com_google_oboe_test_oboetest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject instance) {
jclass sysclazz = env->FindClass("java/lang/System");
jmethodID getPropertyMethod = env->GetStaticMethodID(sysclazz, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
return result;
}
When this method is called the app crashes and I get the error:
JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0xd280e8d5
Step debugging shows that this line causes the crash:
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
What causes this error? And how can I call System.getProperty("os.name") using JNI without getting this error?
The issue is that env->CallStaticObjectMethod is expecting a jstring as its 3rd argument and is instead being supplied with a string literal.
Creating a jstring first
jstring arg = env->NewStringUTF("os.name");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, arg);
fixed the problem.
In my case, I was using a local reference that was created in a function and was used in another function.
For example:
void funA(){
jclass localClass = env->FindClass("MyClass");
}
The above statement returns a local reference of jclass. Suppose we store this in a global variable and use it in another function funB after funA is completed, then this local reference will not be considered as valid and will return "use of deleted local reference error".
To resolve this we need to do this:
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
First, get the local reference and then get the global reference from local reference. globalClass can be used globally (in different functions).
Read Local and global references

Calling several instances of java classes from native C code

I'm trying to call several instances of a java class from c language. The code runs well for a single instance but fails when I try to call several instances of java classes.
There is a jar file and a supporting dll for the java classes, and the java classes are assumed to be a complete blackbox
JNIEnv* create_vm(JavaVM **jvm) {
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[4];
options[0].optionString = "-Djava.compiler=none";
//Path to the java source code
options[1].optionString = "-Djava.class.path=G:\\dtk_testing\\dtk_test\\bin;C:\\Program Files (x86)\\DTK Software\\DTK ANPR SDK\\Bin\\Java\\DTKANPR.jar";
options[2].optionString = "-Djava.library.path=C:\\Windows\\System32";
//options[3].optionString = "-verbose";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 4;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
jint ret =JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
invoking a class using the following method in C:
The value j is a parameter that I pass on to the java code, which in turn calls an image with that particular serial number
void invoke_class(JNIEnv *env, char *str)
{
jclass DTK_ANPR_Test;
jmethodID dtk_anpr;
jint k=2;
jvalue *val=(jvalue *)malloc(sizeof(jvalue));
val->i=(jint *)malloc(sizeof(jint));
(val->i)=k;
DTK_ANPR_Test = (*env)->FindClass(env, "DTKANPRTest");
dtk_anpr = (*env)->GetStaticMethodID(env, DTK_ANPR_Test, "anpr", " (I)V");
(*env)->CallStaticVoidMethodA(env, DTK_ANPR_Test, dtk_anpr, val);
}
Later I call this method, 'invoke_class' wherever I need to run the java class.
I was thinking that running the code in parallel (using openmp or mpi) would do, but I'm still not sure as to how I can proceed further
P.S. I don't have any good idea about openmp or mpi
I think what you mean, is not "instances" since you are invoking a static method, but multiple threads invoking the same static method?
If that is the case, your first invocation will work because the thread used, called JNI_CreateJavaVM which becomes your java main thread. When JVM is created it attaches the current native thread to the JVM.
The JNIEnv that you get, is only valid for that 1 thread which originally created the JVM. If you want to call the same method from additional native threads, you need to attach those threads as well, using the JNI call AttachCurrentThread.
Note, you will have a different JNIEnv instance for each thread.
Hope that helps.
PS: if you are passing a simple jint argument to a java method, I would suggest you use CallStaticVoidMethod(env, DTK_ANPR_Test, dtk_anpr, 2) instead of needlessly allocating memory to pass an integer, which never seems to be released, so this will be a memory leak.

What is the JNI flow for native Thread class functions? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I am new to the JNI area and my aim is to study the native methods of java.lang.Thread such as registerNatives and start0() so that I get to understand at the OS level what are the OS operations that happen when a Thread is
a)created
b)started
So I cam across various tutorials for JNI so as to understand just the basics. It looks like there are 4 main steps
a) write the declaration of the native method(s) in a class (like start0() in Thread.java)
b) use javah tool in jdk/bin to generate a .h file
c) include these generated .h files and other common .h files in jdk/include to the c/c++ project env
d) write the required c++ function.
Please correct me if I have missed a step.
Questions-
1) When I apply the step b) to a standard java file like Thread.java, I couldn't find something like Thread.h in the JDK and it's source. What should be the exact location of the same ? I got the link of Thread.h in apache harmony http://svn.apache.org/repos/asf/harmony/enhanced/sandbox/bootjvm/bootJVM/jni/src/harmony/generic/0.0/include/java_lang_Thread.h and this is exactly the same file which I expected to be there in jdk.
2) I see a file called Thread.C at jdk\src\share\native\java, this file includes the file I expected in point number 1, java_lang_Thread.h. Thread.C file contains code
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
Ideally I would have expected this code to be there in Thread.h, why is it there in Thread.C and what is it's significance ?
3)There is a file called thread.cpp at openjdk\hotspot\src\share\vm\runtime and it contains all the method definitions, now starting from Thread.h
JNIEXPORT void JNICALL Java_java_lang_Thread_start(JNIEnv *, jobject);
how can we map it to Thread.start because I couldn't notice a mapping between start0() to start().
4) Since I am a java programmer and may have hard time understanding c++ code in Thread.CPP, could someone guide me to a link which may contain theory of these methods like set_stack_base(NULL) etc ?
I do not have a link to the jdk or the java threading source. Perhaps if you provided one, I could solve your problem.
However, what I gathered from your question is: "How does the Thread.h link to the Thread.cpp?" If that is what you are asking then think of it this way:
Thread.h just contains a bunch of declarations. Similar to that of a Java interface.
Thread.c contains the implementation of that interface.
As for my second guess as to what you're asking: "How does java create a native thread?".
If I had to take a huge guess at Java creating a thread on Windows, I'd say the definition is either written using WINAPI (windows only) or the C++ stl (portable):
Suppose you have a java Threading class:
public class Threading {
static{ System.LoadLibrary("Threading"); }
private Runnable r = null;
private native void StartNativeThread(Runnable r);
public Threading(Runnable r) {
this.r = r;
}
public void start() {
StartNativeThread(this.r);
}
}
The above class is passed a runnable in its constructor. When you call start, it calls the native function "StartNativeThread" and that function is going to take the runnable as a parameter.
On the C++ side, it will create a thread and that thread will call Runnable.run which it got from the java side.
WINAPI-C++:
//object that will hold all thread information.
struct ThreadParams
{
JNIEnv* env;
jobject runnable;
};
//function that the thread will call.
DWORD __stdcall RunnableThreadProc(void* ptr)
{
ThreadParams* p = reinterpret_cast<ThreadParams*>(ptr); //get our thread info from the parameter.
JNIEnv* env = p->env; //grab our env.
jobject runnable = p->runnable; //grab our runnable object.
delete p; //since we allocated on the heap using new, we must delete from the heap.
//this is because c++ does not have garbage collection.
jclass RunnableInterface = env->GetObjectClass(runnable); //get our java runnable interface instance.
jmethodID Run = env->GetMethodID(RunnableInterface, "run","()V"); //get the run method function pointer.
env->CallObjectMethod(RunnableInterface, Run); //call RunnableInterface.run();
}
JNIEXPORT void JNICALL Java_JNIExample_StartNativeThread(JNIEnv* env, jobject obj, jobject runnable)
{
ThreadParams* ptr = new ThreadParams(); //create an object to store our parameters.
ptr->env = env; //store the env parameter.
ptr->runnable = runnable; //store the runnable object.
//create a thread that calls "RunnableThreadProc" and passes it "ptr" as a param.
CreateThread(0, 0, RunnableThreadProc, reinterpret_cast<void*>(ptr), 0, 0);
}
Now the above looks quite complicated to be completely honest but that is what WINAPI is. It is an API written for windows in the C Language.
If you have a C++x11 compiler and wish to avoid winapi and use STL-C++, this can be done in a couple lines.
Assume that we have the same java class as above, then our function becomes:
JNIEXPORT void JNICALL Java_JNIExample_StartNativeThread(JNIEnv* env, jobject obj, jobject runnable)
{
std::thread([&]{
jclass RunnableInterface = env->GetObjectClass(runnable);
jmethodID Run = env->GetMethodID(RunnableInterface, "run","()V");
env->CallObjectMethod(RunnableInterface, Run);
}).detach();
}
Note that [&]{....} is a Lambda Function. It means a function that can be created inside of another function or parameter.
The above can also be translated / is equivalent to:
void ThreadProc(JNIEnv* env, jobject runnable)
{
jclass RunnableInterface = env->GetObjectClass(runnable);
jmethodID Run = env->GetMethodID(RunnableInterface, "run","()V");
env->CallObjectMethod(RunnableInterface, Run);
}
JNIEXPORT void JNICALL Java_JNIExample_StartNativeThread(JNIEnv* env, jobject obj, jobject runnable)
{
std::thread(ThreadProc, env, obj).detach();
}
Now implementing other things like stop and pause is just as easy. You simply do that on the java side inside your runnable. OR you can do it on the C++ side using WINAPI's TerminateThread and WaitObject and the likes. OR if you choose to use STL-C++ then you'd use an std::condition_variable.
I hope that clears up some things. If you have any further questions, you can just post a comment or make a new thread. Up to you. Otherwise, if I missed something or interpreted your question wrong, then please clarify it.
EDIT.. So for the actual implementation, we can see that Thread.c includes jvm.h. Thus we must find jvm.h and jvm.cpp.
A quick search comes up with:
Thread.h: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/src/share/native/java/lang/Thread.c
JVM.h: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/prims/jvm.h
JVM.cpp: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/jvm.cpp
If we now search for any of the functions from thread.c.. We can see in thread.c that start0 is mapped to JVM_StartThread, and all other thread functions is mapped to JVM_XXXXSomeThreadFunc...
We must now look for these JVM_ functions within JVM.h and JVM.cpp. Once found, you have the source of how it is all done.

JNI - multi threads

I have a JNI wrapper for Java functions that are called from C... I'm trying to call some methods from different threads and I get an error when trying to get a new copy of the JNIEnv pointer... the code I'm using is below and is called in each method:
JNIEnv* GetJniEnvHandle(){
ThreadInfo();
JNIEnv *envLoc;
//if(Thread::CurrentThread->IsBackground || Thread::CurrentThread->IsThreadPoolThread)
jint envRes = vm->GetEnv((void**)&envLoc, JNI_VERSION_1_4);
if(envRes == JNI_OK){
if(ThreadId != Thread::CurrentThread->ManagedThreadId)
jint res = vm->AttachCurrentThread((void**)&envLoc, NULL);
}else{
Log("Error obtaining JNIEnv* handle");
}
return envLoc;
}
The JVM has already been instantiated and this (and other methods) run when being called from the main/initial thread. When I get a value for envRes it holds a -2 when in a sub-thread.
Please refer to the documentation to the chapter Attaching to the VM.
You need to call AttachCurrentThread() for each native thread at least once before you can use any of the JNI functions.
Thread created in Java are already attached.
So I your example whenever the GetEnv call fails call AttachCurrentThread() and you should be fine. Or make sure that whenver you create a sub thread you attach it to the VM.

Categories

Resources