2013-10-24 3 views
0

Я отлаживал эту проблему на пару дней без везения. Я должен упустить что-то довольно очевидное. Я запускаю приложение Swing, упакованное с инструментами упаковки JavaFX 2.2, и оно подключается к C .dll через JNI.Как диагностировать проблему повреждения памяти JNI?

Все было нормально, пока я не захотел добавить функцию для вызова из 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; 
} 

Тогда позже в моем родном коде, после того, как все мои подключения к сокетам инициализированы, и я готов к начать принимать все JNI звонки от Java, я использую метод обратного вызова, чтобы позволить 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); 
... 

Теперь я знаю, функционально, что это работает. Функционально мое приложение в порядке. Но 1 из 20 или около того, он будет аварийно завершен после того, как этот нативный код завершится. Похоже, что это может повредить кучу каждый раз, когда она запускается. Но только иногда эта коррупция вызывает крушение.

Что мне здесь не хватает? Я удаляю свою глобальную ссылку и удаляю указатель. Я прикрепляюсь и отсоединяюсь от темы. Прогуливаясь через отладчик, все выглядит неплохо.

+2

Вы должны тестировать (env! = NULL). – EJP

+3

Вы, вероятно, хотите отсоединиться только в том случае, если вы сделали прикрепление в своей функции. –

+0

К сожалению, ни одно из этих изменений не имело никакого значения. – Splaktar

ответ

2

Это оказалось куча коррупция много уровней библиотек глубоких. Весь код JNI (после некоторых полезных комментариев здесь) был чистым и проблемным.

Проблема оказалась передачей дополнительного аргумента в строке. Эта строка передается по нескольким уровням (библиотеки C) перед анализом и используется для настройки обработки бэкэнд. Я передавал аргумент, который был неизвестен последней библиотеке, и вместо сообщения об ошибке, библиотека пыталась разобрать эту кучу при попытке проанализировать эту строку.

В конце концов, это не имело никакого отношения к Java или JNI. Это был еще один артефакт построения поверх плохо написанных базовых библиотек C. Подобные вещи, к сожалению, распространены, поскольку время не предусмотрено для очистки или рефакторинга. Я просто рад, что теперь могу вернуться на Java :)

Благодарим за помощь. Я действительно прошел через бит JNI с тонкой зубчатой ​​гребенкой и многому научился.

5

IIRC, вы должны передать объект при вызове метода экземпляра. Вы можете захотеть заменить CallVoidMethod на CallStaticVoidMethod.

Хорошо, кажется, ваша проблема не в вызове CallVoidMethod. Мои советы:

  1. попытка сузить проблему путем постепенного закомментировать части кода
  2. исключение процесса после каждого вызова JNI: Используйте ExceptionOccured, ExceptionDescribe и, наконец, ExceptionClear.
  3. дважды проверить использование таНоса/бесплатно (включая strdup, новое, удалять ...)
+0

Я не верю, что это верно из-за того, что указано в http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp16656 и примеры здесь (http : //www.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html). Я тестировал, что моя функция Java статична, но это привело к появлению большего количества проблем с многопотоковой обработкой на стороне Java. – Splaktar

+1

@Splaktar Присмотритесь к странице, которую вы упомянули как «примеры», в частности см. «Реализация C - TestJNICallBackMethod.c». Для GetmethodID нужен объект класса, а CallVoidMethod - объект класса, используемого в GetMethodID. Кажется логичным, когда вы об этом думаете. – manuell

+0

Думаю, я понимаю, что вы сейчас имеете в виду. Я передаю ему jclass, когда мне нужно передать ему работу. Позвольте мне попробовать! – Splaktar

Смежные вопросы