Diferentes Formas de Acessar Bibliotecas Nativas em Java: JNI, JNA e FFM

No desenvolvimento Java, às vezes precisamos interagir com bibliotecas nativas escritas em linguagens como C ou C++. Atualmente se fala muito em inteligência artificial e machine learning, o acesso a bibliotecas nativas pode trazer benefícios significativos, como:
-
Desempenho: As bibliotecas nativas podem executar operações de baixo nível de maneira mais eficiente do que o código Java puro, aproveitando otimizações específicas da arquitetura do processador.
-
Reutilização de Código: Muitas bibliotecas e frameworks estabelecidos, especialmente em áreas como IA e ML, estão disponíveis apenas em linguagens como C e C++. Usar essas bibliotecas pode evitar a necessidade de reescrever funcionalidades complexas em Java.
-
Interoperabilidade: Permite que aplicativos Java se integrem com sistemas e APIs que foram desenvolvidos em outras linguagens, ampliando as capacidades do software.
A seguir, vamos explorar três principais maneiras de acessar bibliotecas nativas em Java: JNI (Java Native Interface), JNA (Java Native Access) e a mais recente API FFM (Foreign Function & Memory API).
JNI (Java Native Interface)
Visão Geral:
JNI é uma interface poderosa e de baixo nível que permite ao código Java chamar funções nativas e vice-versa. Embora ofereça alto desempenho, a sua complexidade e necessidade de código nativo compilado tornam-no mais difícil de usar.
Exemplo de Uso:
Criação do Método Java Nativo:
public class HelloWorldJNI {
static {
System.loadLibrary("helloworld");
}
private native int strlen(String str);
public static void main(String[] args) {
HelloWorldJNI instance = new HelloWorldJNI();
int length = instance.strlen("https://gasparbarancelli.com");
System.out.println("length: " + length);
}
}
Geração do Cabeçalho JNI:
javac HelloWorldJNI.java
javah -jni HelloWorldJNI
Implementação da Função Nativa (C/C++):
#include <jni.h>
#include "HelloWorldJNI.h"
#include <string.h>
JNIEXPORT jint JNICALL Java_HelloWorldJNI_strlen(JNIEnv *env, jobject obj, jstring str) {
const char *nativeString = (*env)->GetStringUTFChars(env, str, 0);
jint length = strlen(nativeString);
(*env)->ReleaseStringUTFChars(env, str, nativeString);
return length;
}
Compilação do Código Nativo:
gcc -shared -o libhelloworld.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloWorldJNI.c
Execução do Programa Java:
java -Djava.library.path=. HelloWorldJNI
Nota sobre JNI e JEP 472:
O JEP 472 propõe restringir o uso de JNI devido a questões de segurança e manutenção. A proposta destaca que JNI permite acesso direto à memória, o que pode causar problemas de segurança e estabilidade na JVM. A restrição ao uso de JNI incentiva a adoção de APIs mais seguras e modernas como o FFM.
JNA (Java Native Access)
Visão Geral:
JNA simplifica o acesso a bibliotecas nativas, eliminando a necessidade de escrever código nativo. Ele mapeia automaticamente funções nativas para interfaces Java, facilitando a integração.
Exemplo de Uso:
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
public class HelloWorldJNA {
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);
long strlen(String str);
}
public static void main(String[] args) {
long length = CLibrary.INSTANCE.strlen("https://gasparbarancelli.com");
System.out.println("length: " + length);
}
}
Para usar JNA, adicione a seguinte dependência no seu pom.xml:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
Mais informações sobre JNA podem ser encontradas no repositório GitHub do JNA.
FFM (Foreign Function & Memory API)
Visão Geral:
FFM é uma API moderna introduzida no projeto Panama, projetada para oferecer uma maneira segura e eficiente de interagir com bibliotecas nativas e acessar memória nativa. É a abordagem mais recente e promissora, onde a release final foi liberada no Java 22.
Exemplo de Uso:
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
public class HelloWorldFFM {
public static void main(String[] args) throws Throwable {
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
MethodHandle strlen = Linker.nativeLinker().downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS));
try (Arena offHeap = Arena.ofConfined()) {
MemorySegment str = offHeap.allocateFrom("https://gasparbarancelli.com");
long length = (long) strlen.invoke(str);
System.out.println("length: " + length);
}
}
}
O FFM é mais seguro em relação ao uso de memória, pois utiliza o conceito de MemorySegment e Arena, para gerenciar a memória nativa de forma segura.
-
MemorySegment: É uma abstração sobre a memória nativa que oferece segurança e controle sobre os dados alocados fora do heap Java.
-
Arena: É um mecanismo para alocação e gerenciamento de memória que garante que os recursos sejam liberados corretamente, evitando vazamentos de memória.
Para mais detalhes, consulte a documentação oficial.
Conclusão
As três abordagens — JNI, JNA e FFM — oferecem diferentes níveis de controle, facilidade de uso e desempenho para interagir com bibliotecas nativas em Java. JNI é ideal para situações que requerem controle e desempenho máximo, embora seja mais complexo e venha a ser restringido pelo JEP 472. JNA simplifica muito a integração com código nativo, sendo excelente para a maioria dos casos de uso. FFM é uma abordagem moderna e promissora, oferecendo segurança e eficiência, e deve ser considerada para novos projetos.