静态c ++对象中的JNI环境指针并连续两次调用一个带有字符串参数的java函数会导致JVM崩溃

Mar*_*røy 2 c++ java java-native-interface jvm

所以在我的评论员要求我终于找到了一个再现我的错误的MCVE.所以一般的设置是Java使用JNI来调用dll,并且dll抓住正在运行的JVM并存储一个指向JNIEnv的指针,它用于调用java类中的方法(从c ++调用的java类)不一定是原始的调用java对象,这就是输入jobject不用于回调的原因).在我进一步解释之前,让我发布所有代码:

JniTest.java

package jnitest;

public class JniTestJava {
  public static void main(String[] args) {

    try {
      System.load("<path-to-dll>");
    } catch (Throwable e) {
      e.printStackTrace();
    }

    DllFunctions dllFunctions = new DllFunctions();
    dllFunctions.setup();
    dllFunctions.singleIntFunctionCall();
    dllFunctions.doubleIntFunctionCall();
    dllFunctions.singleStringFunctionCall();
    dllFunctions.doubleStringFunctionCall();
  }

  public void javaStringFunction(String input){
    System.out.println(input);
  }

  public void javaIntFunction(int input){
    System.out.println(input);
  }
}
Run Code Online (Sandbox Code Playgroud)

DllFunctions.java

package jnitest;

public class DllFunctions{
  public native void singleIntFunctionCall();
  public native void doubleIntFunctionCall();
  public native void singleStringFunctionCall();
  public native void doubleStringFunctionCall();

  public native void setup();
}
Run Code Online (Sandbox Code Playgroud)

JniTestCpp.h

#include <jni.h>
#ifndef _Included_jnitest_JniTestJava
#define _Included_jnitest_JniTestJava
#ifdef __cplusplus

extern "C" {
#endif
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject);
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject);
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject);
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject);
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject);

#ifdef __cplusplus
}
#endif
#endif
Run Code Online (Sandbox Code Playgroud)

JniTestCpp.cpp

#include "JniTestCpp.h"
#include "JniTestClass.h"

JniTestClass jniTestClass;
extern "C"
{
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject) {
    jniTestClass.setup();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject) {
    jniTestClass.callJavaIntFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject) {
    jniTestClass.callJavaIntFunction();
    jniTestClass.callJavaIntFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject) {
    jniTestClass.callJavaStringFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject) {
    jniTestClass.callJavaStringFunction();
    jniTestClass.callJavaStringFunction();
  }
}
Run Code Online (Sandbox Code Playgroud)

JniTestClass.h

#include <jni.h>

class JniTestClass {
  typedef jint(JNICALL * GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
public:
  void setup();
  void callJavaStringFunction();
  void callJavaIntFunction();
  void throwException(jthrowable ex);

private:
  jobject myObject;
  jclass myClass;
  JNIEnv* env;
};
Run Code Online (Sandbox Code Playgroud)

JniTestClass.cpp

#include "JniTestClass.h"
#include <Windows.h>
#include <fstream>

void JniTestClass::setup() {
  jint jni_version = JNI_VERSION_1_4;
  GetCreatedJavaVMs jni_GetCreatedJavaVMs;
  jsize nVMs = 0;

  jni_GetCreatedJavaVMs = (GetCreatedJavaVMs) GetProcAddress(GetModuleHandle(
    TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
  jni_GetCreatedJavaVMs(NULL, 0, &nVMs); 
  JavaVM** buffer = new JavaVM*[nVMs];
  jni_GetCreatedJavaVMs(buffer, nVMs, &nVMs); 
  buffer[0]->GetEnv((void **) &env, jni_version);
  delete buffer;

  myClass = env->FindClass("jnitest/JniTestJava");
  myObject = env->NewObject(myClass, env->GetMethodID(myClass, "<init>", "()V"));
}

void JniTestClass::callJavaStringFunction() {
  jmethodID myMethod = env->GetMethodID(myClass, "javaStringFunction", "(Ljava/lang/String;)V");
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }

  env->CallVoidMethod(myObject, myMethod, env->NewStringUTF("String!"));
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }
}

void JniTestClass::callJavaIntFunction() {
  jmethodID myMethod = env->GetMethodID(myClass, "javaIntFunction", "(I)V");
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }

  env->CallVoidMethod(myObject, myMethod, 1);
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }
}

void JniTestClass::throwException(jthrowable ex) {
  env->ExceptionClear();
  jclass clazz = env->GetObjectClass(ex);
  jmethodID getMessage = env->GetMethodID(clazz,
                                          "toString",
                                          "()Ljava/lang/String;");
  jstring message = (jstring) env->CallObjectMethod(ex, getMessage);
  const char *mstr = env->GetStringUTFChars(message, NULL);

  printf("%s \n", mstr);
  throw std::runtime_error(mstr);
}
Run Code Online (Sandbox Code Playgroud)

这里的意图是JniTestCpp应该只有JNI导出函数而没有声明的类.JniTestClass背后的想法是它应该包含所有JNI指针和变量(对象,类和环境指针)并提供JniTestCpp可以使用的方法.

现在,这个代码的呈现方式dllFunctions.doubleStringFunctionCall();JniTest.java中调用时崩溃,输出如下:

1
1
1
String!
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e306515, pid=1268, tid=8028
#
# JRE version: Java(TM) SE Runtime Environment (7.0_80-b15) (build 1.7.0_80-b15)
# Java VM: Java HotSpot(TM) Client VM (24.80-b11 mixed mode, sharing windows-x86 )
# Problematic frame:
# V  [jvm.dll+0xc6515]
Run Code Online (Sandbox Code Playgroud)

在下面我展示了hs_err_pidXXX.log文件中的10个顶层堆栈帧:

Stack: [0x02150000,0x021a0000],  sp=0x0219f49c,  free space=317k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [jvm.dll+0xc6515]
V  [jvm.dll+0xc66c9]
C  [JniTestCpp.dll+0x13d52]  JNIEnv_::GetMethodID+0x42
C  [JniTestCpp.dll+0x14ecf]  JniTestClass::callJavaStringFunction+0x3f
C  [JniTestCpp.dll+0x16068]      Java_jnitest_DllFunctions_doubleStringFunctionCall+0x28
j  jnitest.DllFunctions.doubleStringFunctionCall()V+0
j  jnitest.JniTestJava.main([Ljava/lang/String;)V+38
v  ~StubRoutines::call_stub
V  [jvm.dll+0x1429aa]
V  [jvm.dll+0x20743e]
Run Code Online (Sandbox Code Playgroud)

让我感到惊讶的是,如果我在JniTestCpp.cpp中没有声明JniTestClass jniTestClass为静态对象,而是声明它并setup()在每个方法中调用它,如下所示,它不会崩溃,但会产生预期的结果.另外,我必须说,我在打电话时工作很奇怪,doubleIntFunctionCall();但不是doubleStringFunctionCall();

JniTestCpp.cpp - 这不会崩溃

#include "JniTestCpp.h"
#include "JniTestClass.h"

extern "C"
{
  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject) {
    JniTestClass jniTestClass;
    jniTestClass.setup();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject) {
    JniTestClass jniTestClass;
    jniTestClass.setup();
    jniTestClass.callJavaIntFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject) {
    JniTestClass jniTestClass;
    jniTestClass.setup();
    jniTestClass.callJavaIntFunction();
    jniTestClass.callJavaIntFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject) {
    JniTestClass jniTestClass;
    jniTestClass.setup();
    jniTestClass.callJavaStringFunction();
  }

  JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject) {
    JniTestClass jniTestClass;
    jniTestClass.setup();
    jniTestClass.callJavaStringFunction();
    jniTestClass.callJavaStringFunction();
  }
}
Run Code Online (Sandbox Code Playgroud)

对不起,很长的帖子,但这是我觉得我能够毫不含糊地提出问题的唯一方法.

UPDATE

在功能上void JniTestClass::callJavaStringFunction(),如果我将其更改为以下内容:

void JniTestClass::callJavaStringFunction() {
  jmethodID myMethod = env->GetMethodID(myClass, "javaStringFunction", "(Ljava/lang/String;)V");
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }

  jstring j_string = env->NewStringUTF("String!");
  env->CallVoidMethod(myObject, myMethod, j_string);
  if (env->ExceptionCheck()) {
    throwException(env->ExceptionOccurred());
  }

  env->DeleteLocalRef(j_string);
}
Run Code Online (Sandbox Code Playgroud)

在那里我现在请DeleteLocalRef()jstring与创建NewStringUTF(),程序仍然崩溃,但打印出此异常的消息:

java.lang.NoSuchMethodError: javaStringFunction
Run Code Online (Sandbox Code Playgroud)

apa*_*gin 6

您的代码中存在多个错误.

  1. jobject myObjectjclass myClass在JNI调用中重用.

    jobjects默认情况下,在JNI方法中创建的所有内容都是本地引用.每当JNI方法返回时,所有本地引用都会自动释放.

    如果要跨方法调用重用jobject(或者jclass也是对象引用),则应使用NewGlobalRef将其转换为全局引用.当不再需要全局引用时,它应该被DeleteGlobalRef删除,否则引用的对象永远不会被垃圾收集.

  2. JNIEnv* 缓存.

    通常,JNIEnv*永远不应该存储以供以后重用.相反,您应该使用JNIEnv*provide作为每个JNI函数的第一个参数.或者,它可以通过GetEnv调用获得.请注意,每个线程都有自己的线程JNIEnv*,不适用于其他线程.