Introducción: Uso de bibliotecas nativas en Java
Una librería nativa es una librería que contiene código compilado
para una arquitectura (nativa) específica. Hay ciertos escenarios, como
las integraciones hardware-software y las optimizaciones de procesos, en
los que el uso de bibliotecas escritas para diferentes plataformas
puede ser muy útil o incluso necesario. Para ello, Java ofrece la
Interfaz Nativa Java (JNI), que permite al código Java que se ejecuta
dentro de una Máquina Virtual Java (JVM) interoperar con aplicaciones y
bibliotecas escritas en otros lenguajes de programación, como C, C++ y
ensamblador. La JNI permite al código Java llamar y ser llamado por
aplicaciones y bibliotecas nativas escritas en otros lenguajes y permite
a los programadores escribir métodos nativos para manejar situaciones
en las que una aplicación no puede escribirse completamente en Java.
Los formatos habituales de las bibliotecas nativas son los archivos
.dll en Windows, .so en Linux y .dylib en plataformas macOS. El lenguaje
convencional para cargar estas bibliotecas en Java se presenta en el
siguiente ejemplo de código.
package rollbar;
public class ClassWithNativeMethod {
static {
System.loadLibrary("someLibFile");
}
native void someNativeMethod(String arg);
}
Java carga las bibliotecas nativas en tiempo de ejecución invocando
el método System.load() o System.loadLibrary(). La principal diferencia
entre ambos es que el último no requiere que se especifique la ruta
absoluta y la extensión de archivo de la biblioteca, sino que se basa en
la propiedad del sistema java.library.path. Para acceder a los métodos
nativos de las bibliotecas cargadas, se utilizan stubs de métodos
declarados con la palabra clave native.
Error UnsatisfiedLinkError: ¿Qué es y cuándo se produce?
Si un programa Java está utilizando una librería nativa pero es
incapaz de encontrarla en tiempo de ejecución por alguna razón, lanza el
error en tiempo de ejecución java.lang.UnsatisfiedLinkError. Más
concretamente, este error se lanza siempre que la JVM es incapaz de
encontrar una definición apropiada en lenguaje nativo de un método
declarado nativo, mientras intenta resolver las bibliotecas nativas en
tiempo de ejecución [2]. El error UnsatisfiedLinkError es una subclase
de la clase java.lang.LinkageError lo que significa que este error es
capturado al inicio del programa, durante el proceso de carga y enlace
de clases de la JVM.
Algunas situaciones habituales en las que se produce este error
incluyen una referencia a las bibliotecas ocijdbc10.dll y ocijdbc11.dll
al intentar conectarse a una base de datos Oracle 10g u 11g con el
controlador OCI JDBC [3], así como la dependencia de la biblioteca
lwjgl.dll utilizada en el desarrollo de juegos y aplicaciones Java que
dependen de algunas bibliotecas C/C++ heredadas.

Cómo solucionar el error UnsatisfiedLinkError
Para averiguar el culpable exacto y solucionar el error UnsatisfiedLinkError, hay que tener en cuenta un par de cosas:
- Asegúrese de que el nombre de la biblioteca y / o la ruta se especifican correctamente.
- Llame siempre a System.load() con una ruta absoluta como argumento.
- Asegúrese de que la extensión de la biblioteca está incluida en la llamada a System.load().
- Compruebe que la propiedad java.library.path contiene la ubicación de la biblioteca.
- Compruebe que la variable de entorno PATH contiene la ruta a la biblioteca.
- Ejecute el programa Java desde un terminal con el siguiente comando:
java -Djava.library.path=«<LIBRARY_FILE_PATH>» -jar
<JAR_FILE_NAME.jar>
Una cosa importante a tener en cuenta es que System.loadLibrary()
resuelve los nombres de archivos de biblioteca de una manera dependiente
de la plataforma, por ejemplo, el fragmento de código en el ejemplo en
la Introducción esperaría un archivo llamado someLibFile.dll en Windows,
someLibFile.so en Linux, etc.
Además, el método System.loadLibrary() busca primero en las rutas
especificadas por la propiedad java.library.path, y luego por defecto en
la variable de entorno PATH.
Ejemplo de Error UnsatisfiedLinkError
El código siguiente es un ejemplo de intento de cargar una biblioteca
nativa llamada libraryFile.dll con el método System.loadLibrary() en
una plataforma Windows OS. La ejecución de este código arroja el error
en tiempo de ejecución UnsatisfiedLinkError con un mensaje que dice «no
libraryFile in java.library.path» sugiriendo que no se pudo encontrar la
ruta a la biblioteca .dll.
package rollbar;
public class JNIExample {
static {
System.loadLibrary("libraryFile");
}
native void libraryMethod(String arg);
public static void main(String... args) {
final JNIExample jniExample = new JNIExample();
jniExample.libraryMethod("Hello");
}
}
Exception in thread "main" java.lang.UnsatisfiedLinkError:
no libraryFile in java.library.path:
C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;
C:\Program Files (x86)\Common Files\Intel\Shared Files\cpp\bin\Intel64;
C:\ProgramData\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;
C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;
C:\Program Files\MySQL\MySQL Utilities 1.6\;
C:\Program Files\Git\cmd;C:\Program Files (x86)\PuTTY\;
C:\WINDOWS\System32\OpenSSH\;...
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2447)
at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:809)
at java.base/java.lang.System.loadLibrary(System.java:1893)
at rollbar.JNIExample.<clinit>(JNIExample.java:6)
Hay un par de enfoques para solucionar este error.
Enfoque #1: Actualizar la variable de entorno PATH
Uno es asegurarse de que la variable de entorno PATH contiene la ruta
al archivo libraryFile.dll. En Windows, esto se puede hacer navegando a
Panel de control → Propiedades del sistema → Avanzadas → Variables de
entorno, encontrando la variable PATH (sin distinguir mayúsculas de
minúsculas) en Variables del sistema, y editando su valor para incluir
la ruta a la biblioteca .dll en cuestión. Para obtener instrucciones
sobre cómo hacer esto en diferentes sistemas operativos, consulte.
Método 2: Comprobación de la propiedad java.library.path
Otro método consiste en comprobar si la propiedad del sistema
java.library.path está establecida y si contiene la ruta a la
biblioteca. Esto se puede hacer llamando a
System.getProperty(«java.library.path») y verificando el contenido de la
propiedad, como se muestra en el siguiente código.
package rollbar;
public class JNIExample {
static {
var path = System.getProperty("java.library.path");
if (path == null) {
throw new RuntimeException("Path isn't set.");
}
var paths = java.util.List.of(path.split(";"));
if (!paths.contains("C:/Users/Rollbar/lib")) {
throw new RuntimeException("Path to library is missing.");
}
System.loadLibrary("libraryFile");
}
native void libraryMethod(String arg);
public static void main(String... args) {
final JNIExample jniExample = new JNIExample();
jniExample.libraryMethod("Hello");
}
}
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Path to library is missing.
at rollbar.JNIExample.<clinit>(JNIExample.java:16)
Técnicamente, la propiedad java.library.path puede ser actualizada
llamando a System.setProperty(«java.library.path», «./lib»), pero como
las propiedades del sistema son cargadas por la JVM antes de la fase de
carga de la clase, esto no tendrá efecto en la llamada
System.loadLibrary(«libraryFile») que intenta cargar la biblioteca en el
ejemplo anterior. Por lo tanto, la mejor manera de resolver el problema
es seguir los pasos descritos en el enfoque anterior.
Método #3: Sobreescribiendo la propiedad java.library.path
Como complemento al enfoque anterior, la única forma efectiva de
establecer explícitamente la propiedad java.library.path es ejecutando
el programa Java con el argumento de línea de comandos -Dproperty=value,
como se muestra a continuación:
java -Djava.library.path=«C:\Users\Rollbar\lib» -jar JNIEjemplo
Y puesto que esto anularía la propiedad system si ya está presente,
cualquier otra librería requerida por el programa para ejecutarse
también debería incluirse aquí.
Método #4: Usar System.load() en lugar de System.loadLibrary()
Por último, sustituir System.loadLibrary() por una llamada a
System.load() que tome la ruta completa de la biblioteca como argumento
es una solución que evita la búsqueda en java.library.path y soluciona
el problema independientemente de cuál fuera la causa inicial del error
UnsatisfiedLinkError.
package rollbar;
public class JNIExample {
static {
System.load("C:/Users/Rollbar/lib/libraryFile.dll");
}
native void libraryMethod(String arg);
public static void main(String... args) {
final JNIExample jniExample = new JNIExample();
jniExample.libraryMethod("Hello");
System.out.println("Library method was executed successfully.");
}
}
Library method was executed successfully.
Sin embargo, no siempre es deseable codificar la ruta a la
biblioteca, por lo que en esos casos es preferible recurrir a otros
métodos.
Resumen
El uso de librerías nativas compiladas para diferentes plataformas es
una práctica común en Java, especialmente cuando se trabaja con
sistemas grandes y de características o rendimiento críticos. El marco
de trabajo JNI permite a Java hacer esto actuando como puente entre el
código Java y las bibliotecas nativas escritas en otros lenguajes. Uno
de los problemas con los que se encuentran los programadores es la
imposibilidad de cargar correctamente estas bibliotecas nativas en su
código Java, momento en el que la JVM desencadena el error en tiempo de
ejecución UnsatisfiedLinkError. Este artículo proporciona una visión de
los orígenes de este error y explica los enfoques pertinentes para
tratar con él.
Seguimiento, análisis y gestión de errores con Rollbar
Gestionar errores y excepciones en el código es todo un reto. Puede
hacer que el despliegue de código de producción sea una experiencia
desconcertante. Ser capaz de rastrear, analizar y gestionar errores en
tiempo real puede ayudarle a proceder con más confianza. Rollbar
automatiza la supervisión y clasificación de errores, lo que hace que
corregir errores de Java sea más fácil que nunca. Inscríbase hoy mismo
Referencias
[1] Oracle, 2021. Java Native Interface Specification Contents,
Introduction. Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html. [Accessed Jan. 11, 2022]
[2] Oracle, 2021. UnsatisfiedLinkError (Java SE 17 & JDK 17). Oracle and/or its affiliates. [Online]. Available: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/UnsatisfiedLinkError.html. [Accessed Jan. 11, 2022]
[3] User 3194339, 2016. UnsatisfiedLinkError: no ocijdbc11 in
java.library.path. Oracle and/or its affiliates. [Online]. Available: https://community.oracle.com/tech/developers/discussion/3907068/unsatisfiedlinkerror-no-ocijdbc11-in-java-library-path. [Accessed Jan. 11, 2022]
[4] User GustavXIII, 2012. UnsatisfiedLinkError: no lwjgl in java.library.path. JVM Gaming. [Online]. Available: https://jvm-gaming.org/t/unsatisfiedlinkerror-no-lwjgl-in-java-library-path/37908. [Accessed Jan. 11, 2022]
[5] Oracle, 2021. How do I set or change the PATH system variable?. Oracle and/or its affiliates. [Online]. Available: https://www.java.com/en/download/help/path.html. [Accessed Jan. 11, 2022]
Artículo traducido para el blog de n4p5t3r
Orginal: https://rollbar.com/blog/java-unsatisfiedlinkerror-runtime-error/#