JVM падает (2 истории про вызов native library)

    Хочу поделиться двумя историями с одинаковым сюжетом, но разными развязками.
    Может быть кому то, у кого тоже падает JVM будет полезно

    1. Native code вызывается из Явы через JNI. Юнит тест — проходит на ура, приложение (GUI, Swing) крэшится.
    Подключаемся через дебагер ddd (это такая оболочка над gdb, ежели кто из яваистов не знает :) ) — видим что падает с длинющим стеком. Выясняем с автором нативной библиотеки, что там они десериализуют через boost (такая библиотека для C++) дерево большой глибины. И там рекурсия.

    Возникает идея (не сразу, 3 дня споров и гугления), что при вызове из приложения стек больше и он переполняется. Находим параметер для JVM: -XX:ThreadStackSize=

    Работает!

    2. Native code вызывается через JNA. Присутствую колбеки обратно в Явы, так как я описывал. Юнит тест бежит, приложение падает!


    Опять — запускаем ddd (процесс такой — вызывать дебаг версию библиотеки, чтобы видеть соурсы, запустить приложение из среды для дебага, поставить брекпоинт в яве, когда остановится узнать номер процесса Явы, подключиться к ней из ddd, выставить в нем breakpoints, продолжить исполнение в ddd, продолжить исполнение в Яве — фух!)

    Видим, что падает при попытке вызвать Ява-колбек. Как же так?! Я знал, что нельзя чтобы колбек собрался gc и сохранил его в поле класса своего сервиса!

    Еще день расследований и выясняется:
    наши умельцы создавали в приложении еще один Spring Context.
    Т.е.
    1. Создавался «правильный» контекст, в нем рождался спринг бин моего сервиса, он регистрировал колбек
    2. Создавался «лишний» контекст, в нем рождался еще один бин моего сервиса (он синглетон, но только в рамках одного контекста! В другом конкексте еще один истанс!) и регистрировал свой колбек
    3. GC собирал лишний контекст, а с ним и мой сервис, а с ним и колбэк
    4. Я вызывал свой сервис (из «правильного» спринг контекста), он вызывал C, тот вызывал колбек. А записан у него был уже собранный мусорщиком колбек из «неправильного» спринг контекста!
    5. Крэш!

    Убираем лишний контекст — работает!

    Поделиться публикацией

    Комментарии 21

      +1
      Хм, когда это boost стал стандартной библиотекой? Вот stl стандартная, даже имя на это намекает, а boost совсем не стандартная.
        0
        Согласен, исправляю
          +4
          де-факто вполне себе стандартная библиотека.
            0
            Они в Комитете работают, на Стандарт языка С++ оказывают сильное влияние.
            0
            >> Находим параметер для JVM: -XX:ThreadStackSize=
            Случайно не с java.lang.OutOfMemoryError: Java heap space падало? Ато была у нас такая плавающая ошибка… я вот сейчас прочитал и подумалось, что ситуация похожая очень…
              0
              Кажется нет (это было год назад, а вот вторая проблема — сегодня).
              Там же явовского эксепшена быть не могло — JVM падал как процесс
                0
                Да, правильно… как то упустил из виду «JVM падает».
              0
              немоного оффтоп: как-то проводили собеседования на вакансию сапортера для нескольких Javа SDK. Среди множества кандитатов-программистов меньше половины знали про опции JVM. Даже не все о Xmx слышали. И этому я никак не мог найти объяснение.
                0
                На эту тему было тут уже уйму топиков. Большая часть программистов — люди не интересующиеся своей работой. Человек пилит свою болванку и не видит дальше своего носа
                0
                Вот. Вот… Сборшик мусора заботиться о Вас: )
                  0
                  GC не заботится о сохранности ссылок для JNI-вызовов.
                    0
                    Как это?
                      +1
                      Вот тут разные нюансы описаны.
                        0
                        Спасибо, статья полезная, но все же с исходным утверждением «GC не заботится о сохранности ссылок для JNI-вызовов» я не соглашусь. JVM заботится о том, чтобы ссылки на Java-объекты из native-методов были валидны даже после GC, а именно:
                        — что локальные ссылки валидны до тех пор, пока не завершится native-метод, в контексте которого эти ссылки созданы;
                        — что глобальные ссылки валидны на протяжении всей работы JVM, пока не вызывана функция DeleteGlobalRef.
                  +1
                  ru.wikipedia.org/wiki/Аппликация
                  А у вас «приложение» или «программа».
                  Извините.
                    +1
                    Исправил. Вроде всюду.
                    Я не в России живу и привык к описанию вех этих дел на английском
                      0
                      ну и тогда в догонку
                      дерево большой глибины.
                    0
                    У меня подобное было. После пары часов танцев с бубном удалось выяснить что JVM падает в момент вызова джавы из нейтив кода. Дабы не танцевать больше с бубном переписал код так, чтобы никаких колбеков не использовалось. Благо архитектура позволяла.

                    Спасибо за объяснение. В следующий раз буду знать как бороться с этой пакостью!
                      0
                      Если можно переписать — это надежнее. У меня легаси код — его переписывать я не могу
                      0
                      Не совсем понял специфику проблемы. Если вызов и callback идут в одном треде, то GC не должен вообще ничего удалять. Если же Вы делаете вызовы асинхронно, сначала передав указатель на callback, и затем ждете, что из JNI он будет вызван, то ссылку на callback нужно «закрепить» при помощи jobject NewGlobalRef(JNIEnv *env, jobject obj);
                        0
                        Это был JNA, а не JNI. И колбек там чисто явовский

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое