你的位置:首页 > 操作系统

[操作系统]jni调试3(线程调试env变量问题)


jni层调试线程死机原因

一,导致死机原因:

  jni层中  线程函数中  只要添加调用env 的函数 ,,就会死机  
 
二,解决方法
第一我们应该理解:
①(独立性) JNIEnv 是一个与线程相关的变量,即线程A有一个 JNIEnv变量, 线程B也有一个JNIEnv变量,由于线程相关,所以A线程不能使用B线程的 JNIEnv 结构体变量。那么如何保证了每个线程JNIEnv的独立性呢?
 

一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回,同时把JNI接口的指针JNIEnv *env(虚拟机环境指针),和jobject obj保存在DLL中的变量里.一段时间后,DLL中的消息接收线程接收到服务器发来的消息,并试图通过保存过的env和obj来调用先前的java对象的方法(相当于JAVA回调方法)来处理此消息此时程序会突然退出(崩溃).

即前台JAVA线程发送消息,后台线程处理消息,归属于两个不同的线程,不能使用相同的JNIEnv变量,这里可以利用一个机制: 利用全局的 JavaVM * 指针得到当前线程的 JNIEnv* 指针,与在C++中两个线程使用TLS进行局部存储类似的原理。


具体方法:

获取全局的JavaVM变量:

/* Our VM */
JavaVM *g_vm;

env->GetJavaVM(&g_vm); //来获取JavaVM指针.获取了这个指针后,将该JavaVM保存起来。(转录)

 


②(公共性) 先了解TLS(thread-local storage)

线程是执行的单元,同一个进程内的多个线程共享了进程的地址空间,线程一般有自己的栈,但是如果想要实现某个全局变量在不同的线程之间取不同的值,而且不受影响。一种办法是采用线程的同步机制,如对这个变量的读写之处加临界区或者互斥量,但是这是以牺牲效率为代价的,能不能不加锁呢?线程局部存储就是干这个的。



 解决以上两个问题:
1首先定义全局变量
namespace android{    static JavaVM* gJavaVM = NULL; //定义一个全局Java VM引用对象    static jobject gJavaObj = NULL; //定义一个全局Java object对象,对于java层的类对象
    ......

 2其次在调用某个线程的函数中定义:(保证线程在进程中资源的公共性,这两句是把参数传给所开线程)

JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj) {   env->GetJavaVM(&gs_jvm); //保存到全局变量中JVM    //直接赋值obj到DLL中的全局变量是不行的,应该调用以下函数:    gs_object=env->NewGlobalRef(obj);   HANDLE ht=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFun,0,NULL,NULL); }

 


3在线程函数中 引用:  (保证线程函数 对应  env  和class)
void WINAPI ThreadFun(PVOID argv)//JNI中线程回调这个方法  {  JNIEnv *env;  gs_jvm->AttachCurrentThread((void **)&env, NULL);  //对应这几句说白了就是从上面函数中把变量取出来在该线程中使用 jclass cls = env->GetObjectClass(gs_object);  //获取JAVA线程中的全局对象 jfieldID fieldPtr = env->GetFieldID(cls,"value","I");  // 获取JAVA对象  while(1)  {   Sleep(100);   //这里改变JAVA对象的属性值(回调JAVA)    env->SetIntField(gs_object,fieldPtr,(jint)gs_i++);  } }

 三,我们在网上看到有些关于jni的程序资料用了(*env)->;

我们需要谨记:在linux下如果.c文件中用 “env->” 编译会找不到此结构,必须用“(*env)->”,或者改成.cpp文件,以 c++的方式来编译。

 


------------------------------------------------------------------------