Learn · concepts
O que é JNI? Interface Nativa Java para Modders
A JNI (Java Native Interface) é a ponte nativa do Java para chamar C/C++ a partir da JVM e vice-versa. É a forma padrão como um mod do Minecraft carrega uma biblioteca nativa.
O que é JNI em termos simples?
JNI (Java Native Interface) é a parte do Java que conecta dois mundos: bytecode gerenciado na JVM e código de máquina compilado escrito em C ou C++. Um método recebe a palavra-chave native e nenhum corpo, e em tempo de execução a JVM o vincula a uma função correspondente em uma biblioteca nativa carregada. A partir daí, ambos os lados trocam argumentos e valores de retorno.
A fronteira não é unidirecional. A função nativa recebe um ponteiro JNIEnv, e esse identificador é o portal de volta para o Java: ele expõe chamadas para leitura e escrita de campos Java, invocação de métodos Java, construção de objetos e lançamento de exceções. Assim, a mesma ponte que permite ao Java chamar para fora também permite ao C chamar para dentro.
Por que um mod ou cliente do Minecraft tocaria código nativo?
A maioria do Java nunca precisa de JNI. Um mod o utiliza quando a JVM sozinha não consegue realizar a tarefa, geralmente gráficos, áudio, integração com o sistema operacional ou uma rotina que precisa ser rápida. A biblioteca nativa faz o trabalho pesado e o JNI é a maneira padrão de carregá-la e chamá-la.
Reutilize uma biblioteca C comprovada
Uma base de código nativa e madura já resolve o problema. Reescrevê-la em Java seria um esforço desperdiçado, então o mod se vincula a ela.
Acesse o sistema operacional
Alguns recursos da plataforma não possuem API Java pura. Uma chamada nativa se torna a única via para acessá-los.
Acelera um caminho quente
Uma rotina numérica apertada ou de baixo nível pode rodar mais rápido como código nativo compilado do que como bytecode na JVM.
Hardware e drivers
Renderização, som e acesso a dispositivos frequentemente estão por trás de bibliotecas nativas que a JNI expõe ao lado Java.
Como funciona uma chamada JNI de ponta a ponta?
O fluxo é idêntico em todas as plataformas: declare o método em Java, compile uma função C correspondente em uma biblioteca compartilhada, carregue-a em tempo de execução, permita que a JVM os vincule por nome e, em seguida, chame através da barreira. Os nomes das funções seguem um padrão fixo para que a JVM possa resolvê-los, e um arquivo de cabeçalho gerado especifica a assinatura exata a ser implementada.
| Etapa | O que acontece |
|---|---|
| Declarar | Um método Java é escrito com a palavra-chave native e deixado vazio no lado Java. |
| Construir | Uma função C ou C++ é compilada em uma biblioteca compartilhada: .dll no Windows, .so no Linux, .dylib no macOS. |
| Carregar | O Java chama System.loadLibrary para carregar essa biblioteca durante a execução do programa. |
| Vincular | A JVM corresponde cada método native à sua função C por meio de uma assinatura ofuscada. |
| Chamar | O Java invoca o método; o controle cruza para o código nativo e retorna com um resultado. |
Qual o custo do JNI para você?
JNI é poderoso e possui arestas afiadas. Como o código nativo é executado fora da rede de segurança da JVM, uma falha ali derruba todo o processo em vez de lançar uma exceção capturável. A memória alocada pelo lado nativo é invisível para o coletor de lixo, então vazamentos se acumulam a menos que sejam liberados manualmente. Cada travessia acarreta uma sobrecarga, o que torna a chamada através da fronteira dentro de um loop apertado cara. E as compilações nativas são travadas na plataforma: um .dll do Windows não será carregado no Linux sem uma compilação separada.
O que compra
- Acesso direto a bibliotecas C e C++ existentes
- Um caminho para recursos do sistema operacional e do hardware que a JVM não alcança
- Execução em velocidade nativa para as rotinas mais importantes
O que custa
- Uma falha nativa pode derrubar toda a JVM
- Gerenciamento manual de memória sem um sistema de coleta de lixo de apoio
- Builds específicos para cada plataforma para manter e distribuir
- Sobrecarga que cruza fronteiras em loops quentes
JNI vs. a API de Funções Estrangeiras e Memória
O JNI é o mecanismo original, mas já não é o único. As versões mais recentes do Java incluem a API de Funções Estrangeiras e Memória (FFM), projetada para chamar código nativo com menos código repetitivo e garantias de segurança mais rigorosas. O JNI ainda domina as ferramentas existentes porque está presente em todos os lugares e é amplamente compreendido.
| JNI | API FFM | |
|---|---|---|
| Idade | Original, presente desde os primeiros lançamentos do Java | Moderna, adicionada em lançamentos recentes |
| Código repetitivo | Cabeçalhos gerados, funções C com nomes ofuscados | Handles de métodos definidos em Java, sem código C intermediário |
| Segurança da memória | Manual; erros podem corromper a JVM | Segmentos limitados com verificações mais fortes |
| Adoção | Ubíquo em bibliotecas existentes | Crescente, em novas bases de código |
Perguntas Frequentes
Está integrado. O JNI é enviado com o JDK e o JVM, então o lado Java não precisa de nada extra. Compilar a parte nativa ainda requer uma cadeia de ferramentas C ou C++ para cada plataforma de destino.
Não. A parte Java permanece portátil, mas uma biblioteca nativa é compilada para um sistema operativo e CPU específicos. Distribuir em várias plataformas significa construir e empacotar um .dll, .so e .dylib separados.
Não. O JNI é o mecanismo original. Versões recentes do Java adicionam a API de Funções e Memória Estrangeiras, que visa ser mais segura e exigir menos código repetitivo, mas o JNI continua amplamente utilizado e bem compreendido.
Quando a JVM não consegue fazer algo diretamente (conversar com uma GPU ou backend de áudio, acessar uma API exclusiva da plataforma ou executar uma rotina crítica para o desempenho), um mod carrega uma biblioteca nativa e a invoca através do JNI.
Construído por pessoas que realmente leem os detalhes internos da JVM.