C 如何诊断JNI内存损坏问题?

C 如何诊断JNI内存损坏问题?,c,java-native-interface,C,Java Native Interface,我已经调试这个问题好几天了,没有任何运气。我一定错过了一些相当明显的东西。我正在运行一个用JavaFX2.2打包工具打包的Swing应用程序,它通过JNI连接到C.dll 一切都很好,直到我想添加一个从C调用回Java的函数。当我这样做时,我开始出现内存损坏问题。下面是错误,后面是我的新JNI代码: # # A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATIO

我已经调试这个问题好几天了,没有任何运气。我一定错过了一些相当明显的东西。我正在运行一个用JavaFX2.2打包工具打包的Swing应用程序,它通过JNI连接到C.dll

一切都很好,直到我想添加一个从C调用回Java的函数。当我这样做时,我开始出现内存损坏问题。下面是错误,后面是我的新JNI代码:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x7c82c912, pid=7424, tid=4828
#
# JRE version: Java(TM) SE Runtime Environment (7.0_40-b43) (build 1.7.0_40-b43)
# Java VM: Java HotSpot(TM) Client VM (24.0-b56 interpreted mode windows-x86 )
# Problematic frame:
# C  [ntdll.dll+0x2c912]
#
# Core dump written.
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x008e9800):  JavaThread "main" [_thread_in_vm, id=4828, stack(0x00030000,0x00130000)]

siginfo: ExceptionCode=0xc0000005, reading address 0x00000000

Registers:
EAX=0x10d3b510, EBX=0x008e0000, ECX=0x00000000, EDX=0x00000000
ESP=0x000ff98c, EBP=0x000ff998, ESI=0x10d3b508, EDI=0x10d5d000
EIP=0x7c82c912, EFLAGS=0x00010246

Top of Stack: (sp=0x000ff98c)
0x000ff98c:   008e0000 00000008 008e0004 000ff9d0
0x000ff99c:   7c8338a2 00000000 10d5d000 000ff9c4
0x000ff9ac:   00000000 00001000 008e0178 008e0000
0x000ff9bc:   0cff0304 0706ff12 00001000 10a80000
0x000ff9cc:   00000000 000ffbfc 7c82b46b 038e0000
0x000ff9dc:   00008000 00007ff4 008e5458 00007ff4
0x000ff9ec:   7c829dc9 008e0178 008e0178 10c375c0
0x000ff9fc:   7c8274b9 77e6958b 000ffa2c 000ffa0c 

Instructions: (pc=0x7c82c912)
0x7c82c8f2:   3d 00 fe 00 00 0f 87 75 dc ff ff 80 7d 14 00 0f
0x7c82c902:   85 53 82 02 00 8b 4e 0c 8d 46 08 8b 10 89 4d 08
0x7c82c912:   8b 09 3b 4a 04 89 55 0c 0f 85 86 4f 01 00 3b c8
0x7c82c922:   0f 85 7e 4f 01 00 56 53 e8 c9 d6 ff ff 8b 45 0c 


Register to memory mapping:

EAX=0x10d3b510 is an unknown value
EBX=0x008e0000 is an unknown value
ECX=0x00000000 is an unknown value
EDX=0x00000000 is an unknown value
ESP=0x000ff98c is pointing into the stack for thread: 0x008e9800
EBP=0x000ff998 is pointing into the stack for thread: 0x008e9800
ESI=0x10d3b508 is an unknown value
EDI=0x10d5d000 is an unknown value


Stack: [0x00030000,0x00130000],  sp=0x000ff98c,  free space=830k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [ntdll.dll+0x2c912]
C  [ntdll.dll+0x338a2]
C  [ntdll.dll+0x2b46b]
C  [MSVCR100.dll+0x10269]
V  [jvm.dll+0x145f0c]
V  [jvm.dll+0x76d81]
V  [jvm.dll+0x76f8c]
V  [jvm.dll+0x772ea]
V  [jvm.dll+0x8b674]
V  [jvm.dll+0x188b8a]
V  [jvm.dll+0x156226]
V  [jvm.dll+0x48950]
V  [jvm.dll+0x4b236]
V  [jvm.dll+0x4c094]
V  [jvm.dll+0x4c205]
V  [jvm.dll+0x9de75]
V  [jvm.dll+0xa3cae]
V  [jvm.dll+0xa3b20]
V  [jvm.dll+0xa6d30]
V  [jvm.dll+0xa72f8]
V  [jvm.dll+0x70dfe]
V  [jvm.dll+0x71666]
V  [jvm.dll+0x71927]
V  [jvm.dll+0x6dac0]
...

---------------  P R O C E S S  ---------------

Java Threads: ( => current thread )
  0x10c88400 JavaThread "Framework Connection" [_thread_in_native, id=5452, stack(0x117a0000,0x118a0000)]
  0x10c7e800 JavaThread "TimerQueue" daemon [_thread_blocked, id=5544, stack(0x11680000,0x11780000)]
  0x10bca800 JavaThread "AWT-EventQueue-0" [_thread_blocked, id=7748, stack(0x11560000,0x11660000)]
  0x10bbc800 JavaThread "Image Fetcher 0" daemon [_thread_blocked, id=808, stack(0x11460000,0x11560000)]
  0x10ab0400 JavaThread "AWT-Windows" daemon [_thread_in_native, id=3040, stack(0x112b0000,0x113b0000)]
  0x10b58800 JavaThread "AWT-Shutdown" [_thread_blocked, id=5728, stack(0x111b0000,0x112b0000)]
  0x0f56b800 JavaThread "Java2D Disposer" daemon [_thread_blocked, id=5440, stack(0x110b0000,0x111b0000)]
  0x0f52e000 JavaThread "Service Thread" daemon [_thread_blocked, id=7628, stack(0x10880000,0x10980000)]
  0x0f528400 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=5092, stack(0x10780000,0x10880000)]
  0x0f526800 JavaThread "Attach Listener" daemon [_thread_blocked, id=1612, stack(0x10680000,0x10780000)]
  0x0f525000 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=8040, stack(0x10580000,0x10680000)]
  0x0f523800 JavaThread "Surrogate Locker Thread (Concurrent GC)" daemon [_thread_blocked, id=7456, stack(0x10480000,0x10580000)]
  0x0f513000 JavaThread "Finalizer" daemon [_thread_blocked, id=6404, stack(0x10380000,0x10480000)]
  0x0f50d000 JavaThread "Reference Handler" daemon [_thread_blocked, id=4892, stack(0x10280000,0x10380000)]
=>0x008e9800 JavaThread "main" [_thread_in_vm, id=4828, stack(0x00030000,0x00130000)]

Other Threads:
  0x0f50b800 VMThread [stack: 0x10180000,0x10280000] [id=5676]
  0x0f538c00 WatcherThread [stack: 0x10980000,0x10a80000] [id=3400]

VM state:not at safepoint (normal execution)

VM Mutex/Monitor currently owned by a thread: None

Heap
 par new generation   total 36864K, used 10756K [0x03380000, 0x05b80000, 0x05b80000)
  eden space 32768K,  32% used [0x03380000, 0x03e01100, 0x05380000)
  from space 4096K,   0% used [0x05380000, 0x05380000, 0x05780000)
  to   space 4096K,   0% used [0x05780000, 0x05780000, 0x05b80000)
 concurrent mark-sweep generation total 90112K, used 0K [0x05b80000, 0x0b380000, 0x0b380000)
 concurrent-mark-sweep perm gen total 12288K, used 7375K [0x0b380000, 0x0bf80000, 0x0f380000)

Card table byte_map: [0x00fa0000,0x01010000] byte_map_base: 0x00f86400

Polling page: 0x008f0000

Code Cache  [0x01080000, 0x010d0000, 0x03080000)
 total_blobs=184 nmethods=0 adapters=155 free_code_cache=32453Kb largest_free_block=33232576

Compilation events (0 events):
No events

GC Heap History (0 events):
No events

Deoptimization events (0 events):
No events

Internal exceptions (10 events):
Event: 0.547 Thread 0x008e9800 Threw 0x03996270 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85978 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85b10 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85c78 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44b08 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44ca0 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44e08 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b863f8 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b86590 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b866f8 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717

Events (10 events):
Event: 2.091 loading class 0x10d25cb0
Event: 2.091 loading class 0x10d25cb0 done
Event: 2.092 loading class 0x10d28388
Event: 2.092 loading class 0x10d28388 done
Event: 2.095 loading class 0x10d283e8
Event: 2.095 loading class 0x10d283e8 done
Event: 2.096 loading class 0x10c44670
Event: 2.096 loading class 0x10c44670 done
Event: 2.096 loading class 0x10d27cc8
Event: 2.096 loading class 0x10d27cc8 done
我有一个.h文件来声明我的全局变量:

extern jclass javaEntryPointClass;
extern jobject javaEntryPointObject;
extern JavaVM* cachedJVM;
我在.c文件中定义全局变量:

// Required definition of the global variables declared in .h
jclass javaEntryPointClass = NULL;
jobject javaEntryPointObject = NULL;
JavaVM* cachedJVM = NULL;
我有一个JNI_OnLoad函数来保存指向JavaVM的指针:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    cachedJVM = jvm;

    return JNI_VERSION_1_6;
}
我有另一个从Java调用的函数,我在其中存储指向jclass的指针:

JNIEXPORT jint JNICALL Java_com_foo_FrameworkServices_Connect(
    JNIEnv *env, jobject obj, jstring string)
{
    jclass cls1 = NULL;
    PSTR szCmdLine = NULL;
    jboolean isCopy = FALSE;

    const char *str = (*env)->GetStringUTFChars(env, string, &isCopy);
    szCmdLine = (CHAR*)str;

    cls1 = (*env)->GetObjectClass(env, obj);
    if (cls1 == NULL)
        return -1;

    javaEntryPointClass = (*env)->NewGlobalRef(env, cls1);
    if (javaEntryPointClass == NULL)
        return -2;

    javaEntryPointObject = (*env)->NewGlobalRef(env, obj);
    if (javaEntryPointObject == NULL)
        return -3;

    SomeLongRunningFunctionThatNeverEndsUntilTheProgramDoes(szCmdLine);

    (*env)->ReleaseStringUTFChars(env, string, str);

    return 0;
}
然后,在我的本机代码中,在我的所有套接字连接都已初始化并且我准备好开始接受来自Java的所有JNI调用之后,我使用回调方法让Java知道我已经准备好了:

    JNIEnv *env = NULL;
    jmethodID mid = NULL;
    int envStatus = 0;
    int attached = 0;

    // Get a current handle to the JNI environment.
    envStatus = (*cachedJVM)->GetEnv(cachedJVM, (void **)&env, JNI_VERSION_1_6);
    if (envStatus == JNI_EDETACHED)
    {
        // If we're not attached, try to attach to the current thread.
        (*cachedJVM)->AttachCurrentThread(cachedJVM, (void **) &env, NULL);
            attached = 1;
    }

    // Make sure the JNIEnv object we have isn't NULL.
    if (env != NULL)
    {
            mid = (*env)->GetMethodID(env, javaEntryPointClass, "callback", "()V");
            if (mid != NULL)
            {
                    // Call Java to tell it that GUI is ready to process requests.
                    (*env)->CallVoidMethod(env, javaEntryPointObject, mid);
            }

            // Free the global references so that Java can garbage collect.
            if (javaEntryPointClass != NULL)
            {
                    (*env)->DeleteGlobalRef(env, javaEntryPointClass);
                    javaEntryPointClass = NULL;
            }
            if (javaEntryPointObject != NULL)
            {
                    (*env)->DeleteGlobalRef(env, javaEntryPointObject);
                    javaEntryPointObject = NULL;
            }
    }

    // Detach the current thread from the JavaVM. Must be done before exiting thread.
    if (attached == 1)
        (*cachedJVM)->DetachCurrentThread(cachedJVM);
...
现在我知道这在功能上是可行的。从功能上来说,我的申请很好。但是,大约20次中有1次,它会在本机代码完成后不久崩溃。看起来每次运行堆时都可能会损坏堆。但只有在某些时候,腐败才会导致崩溃


我错过了什么?我正在删除我的全局引用并清空指针。我正在从线上连接和分离。通过调试程序,情况看起来相当不错。

IIRC,调用实例方法时必须传递一个对象。您可能希望用CallStaticVoidMethod替换CallVoidMethod

好的,您的问题似乎不在CallVoidMethod调用中。 我的建议是:

  • 通过逐步注释掉部分代码,试图缩小问题范围
  • 每次JNI调用后处理异常:使用ExceptionOccured、ExceptionDescripte和finally ExceptionClear
  • 仔细检查malloc/free的使用情况(包括strdup、new、delete…)

  • IIRC,调用实例方法时必须传递一个对象。您可能希望用CallStaticVoidMethod替换CallVoidMethod

    好的,您的问题似乎不在CallVoidMethod调用中。 我的建议是:

  • 通过逐步注释掉部分代码,试图缩小问题范围
  • 每次JNI调用后处理异常:使用ExceptionOccured、ExceptionDescripte和finally ExceptionClear
  • 仔细检查malloc/free的使用情况(包括strdup、new、delete…)

  • 事实证明,这是多层次库的堆损坏。所有的JNI代码(在这里有一些有用的注释之后)都是干净的、没有问题的

    问题是在字符串中传递了一个额外的参数。该字符串在被解析并用于配置后端处理之前会被传递到两个级别(C库)。我传递了一个最终库未知的参数,库在试图解析这个字符串时破坏了堆,而不是提供错误消息

    最终,它与Java或JNI无关。它只是在编写糟糕的底层C库之上构建的另一个工件。不幸的是,这类事情很常见,因为没有为清理或重构编入预算。我很高兴现在可以回到Java:)


    谢谢你在这方面的帮助。我真的很仔细地研究了JNI,学到了很多东西。

    这被证明是许多层次的库深度的堆损坏。所有的JNI代码(在这里有一些有用的注释之后)都是干净的、没有问题的

    问题是在字符串中传递了一个额外的参数。该字符串在被解析并用于配置后端处理之前会被传递到两个级别(C库)。我传递了一个最终库未知的参数,库在试图解析这个字符串时破坏了堆,而不是提供错误消息

    最终,它与Java或JNI无关。它只是在编写糟糕的底层C库之上构建的另一个工件。不幸的是,这类事情很常见,因为没有为清理或重构编入预算。我很高兴现在可以回到Java:)


    谢谢你在这方面的帮助。我真的很仔细地研究了JNI位,学到了很多东西。

    您应该测试(env!=NULL)。您可能只想在函数中附加时分离。不幸的是,这些更改都没有任何区别。您应该测试(env!=NULL)。您可能只想在函数中附加时分离。不幸的是,这两个更改都没有任何区别。我不相信这是真的,因为这里的说明和示例()。我测试了如何使Java函数保持静态,但这在Java端引入了更多的多线程问题。@Splaktar请仔细查看您提到的“示例”页面,特别是“C实现-TestJNICallBackMethod.C”。GetmethodID需要类对象,CallVoidMethod需要GetmethodID中使用的类的对象。你仔细想想,似乎合乎逻辑。我想我现在明白你的意思了。当我需要通过作业对象时,我会通过jclass。让我试试看@Splaktar您的“回调”Java方法不是静态的?我不相信这是真的,因为在和这里的示例()中说明了这一点。我测试了如何使Java函数保持静态,但这在Java端引入了更多的多线程问题。@Splaktar请仔细查看您提到的“示例”页面,特别是“C实现-TestJNICallBackMethod.C”。GetmethodID需要类对象,CallVoidMethod需要GetmethodID中使用的类的对象。你仔细想想,似乎合乎逻辑。我想我现在明白你的意思了。当我需要通过作业对象时,我会通过jclass。让我试试看@Splaktar您的“回调”Java方法是