JVM no funciona como se espera con código JNI C++ que contiene una clase llamada " Node"


Algunos compañeros de equipo y yo no hemos podido entender por qué el siguiente fragmento de código no dará la salida correcta al usar las versiones de JVMs 1.6u23 a 1.6u31 (la última a partir de esta publicación). Este fragmento de código representa una simplificación de un problema mayor:

ACTUALIZACIÓN: Se modificó ligeramente el ejemplo para poner el foco en el problema de que "virtual_function()" no parece ser llamado.

ACTUALIZACIÓN: Simplificado el ejemplo aún más basado en comentarios hasta la fecha.

NodeTester.cpp:

#include <iostream>
#include <jni.h>

class Node {
  public:
    Node () :m_counter(0) {}
    virtual ~Node () {}

    virtual void virtual_function () {
      m_counter += 10;
    }

    void non_virtual_function () {
      m_counter += 1;
    }

    int get_counter () {
      return m_counter;
    }

  private:
    int m_counter;

};

extern "C" {
  JNIEXPORT void JNICALL Java_NodeTester_testNode (JNIEnv *jni_env_rptr, 
                                                   jclass java_class) {
    Node *node_rptr = new Node();
    node_rptr->non_virtual_function();
    node_rptr->virtual_function();

    std::cout << node_rptr->get_counter() << std::endl;

    delete node_rptr;
  }
}

NodeTester.java:

public class NodeTester {
  public static native void testNode ();

  static {
    System.loadLibrary("nodetester");
  }

  public static final void main (String[] args) {
    NodeTester.testNode();
  }
}

Salida esperada:

11

Salida Real con JVM 1.6u23 a través de 1.6u31:

1

Parece que la JVM está construyendo incorrectamente el objeto "Nodo" dentro del JNI; aunque es posible que este código tenga algo incorrecto sobre su uso del JNI. Cuando la clase "Node" obtiene más funcionalidad agregada (por ejemplo, más atributos, operaciones virtuales y no virtuales adicionales), podemos causar un error de segmentación, en lugar de solo salida incorrecta. Estamos compilando el código cpp en una biblioteca de objetos compartidos de 64 bits de RedHat Linux usando g++, y ejecutando el código java con la VM del servidor de 64 bits. Tenga en cuenta que en JVMs 1.6u20 a 1.6u22, esto produce la salida esperada. No he probado ninguna versión anterior.

Hemos decidido poner una recompensa por esta pregunta! Aquí hay más información sobre lo que ya sabemos:

  • JVMs 1. 6u22 (y anteriores) producción esperada resultados
  • Cambiar el nombre de "Nodo" o ponerlo en un espacio de nombres produce resultados esperados
  • La asignación de un objeto" Node " en la pila en lugar del montón en la función JNI produce resultados esperados
  • No hay problemas con los componentes no virtuales de la clase "Node"

Desafortunadamente para nosotros, ninguno de estos elementos conducen a soluciones viables - el "problema más grande" al que aludí fue que estamos tratando con una gran base de código existente con una clase C++ llamada " Node", a la que tenemos que acceder a través del JNI. También probamos varias opciones de compilador de g++ y javac, y varias opciones de JVM, sin éxito (aunque si alguien tropieza con una que realmente produzca los resultados esperados, esta sería una solución aceptable).

 24
Author: Tom, 2012-03-09

5 answers

Ok, esta no es una respuesta perfecta, pero si no tenemos nada mejor, lo siguiente puede ayudar. Como se explicó un poco en los otros comentarios, el quid del problema radica en dos clases distintas de C++ ambas llamadas Node en el espacio de nombres global, una de OpenJDK o SunJDK 1.6u23 y arriba en RedHat Linux (al menos) y otra de otra biblioteca, las cuales necesitan tener sus símbolos compartidos con otras bibliotecas. Para obtener nuestros símbolos cargados antes del JDK, podemos establecer la variable de entorno LD_PRELOAD, por llamando por ejemplo:

LD_PRELOAD=libTheNodeTester.so java ...

Pero esto puede bloquear el JDK, si realmente comienza a usar nuestros símbolos como si fueran los de sus bibliotecas...

 7
Author: Samuel Audet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-20 04:27:12

Mirando el código del HotSpot hay un nodo.hpp / nodo.cpp que declara una clase de nodo sin un espacio de nombres.
Tal vez haya una colisión con las funciones virtuales puras.
No tengo suficiente conocimiento de Ministros voluntarios para cavar más...

 6
Author: Chen Harel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-19 14:25:55

Parece que la JVM está construyendo incorrectamente el objeto" Node" dentro de JNI

Sea claro. La JVM no construye el objeto "Node" en absoluto. El sistema de tiempo de ejecución de C++ hace eso.

He usado toneladas de C++ dentro de JNI sin ningún otro problema que los que causé.

Lo primero que viene a la mente es que no está comprobando el resultado del operador 'nuevo' para null. Eso no afectará a la función no virtual, solo verá 'esto', que no están usando, como null, pero evitará el envío de la función virtual, ya que la indirección a través de la vtable será seg-fault.

Por qué sería nulo es otra pregunta ...

 2
Author: user207421,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-09 02:47:49

Para las patadas, intente vaciar stdout y stderr antes de salir del código nativo. Estoy pensando que tal vez la JVM está saliendo con datos en algún búfer de salida.

 1
Author: Java42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-12 21:15:51

¿Puede agregar una capa de envoltura en código nativo? es decir, escribir una clase C++ para proxy de la clase Node y llamar desde java en lugar de llamar a Node directamente.

En el wrapper puede colocar un espacio de nombres en las importaciones para evitar ambigüedades ( http://www.glenmccl.com/ns_comp.htm, por ejemplo).

 0
Author: vladimir e.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-20 10:34:01