Translate

Mostrando las entradas con la etiqueta FFM API. Mostrar todas las entradas
Mostrando las entradas con la etiqueta FFM API. Mostrar todas las entradas

domingo, 14 de junio de 2026

FFM API en Java: Accediendo a Memoria Nativa y Código Nativo sin JNI


La FFM API (Foreign Function & Memory API) es una de las incorporaciones más interesantes de Java moderno.


Su objetivo es permitir que una aplicación Java pueda:

  • Llamar funciones escritas en C.
  • Acceder a memoria fuera del heap de la JVM.
  • Reemplazar gran parte del uso de JNI.
  • Interactuar con librerías nativas de forma más segura y eficiente.


La FFM API forma parte del proyecto Project Panama.

Pero demos un paso para atras,  ¿Por qué existía JNI?


Durante años, si una aplicación Java necesitaba:

  • acceder a una librería en C,
  • usar código del sistema operativo,
  • trabajar con drivers,
  • o ejecutar operaciones de bajo nivel,

la única opción era usar JNI (Java Native Interface)


El problema es que JNI:

  • es complejo,
  • verboso,
  • inseguro,
  • difícil de debuggear,
  • y propenso a crashes de la JVM.


Además, obliga a escribir:

  • código Java,
  • código C,
  • headers,
  • compilación nativa,
  • glue code.


Puff si no habre puteado con JNI.


¿Qué propone la FFM API?

La FFM API permite hacer esto:

printf("Hola desde C!\n");


...directamente desde Java.


Y además:

  • manejar memoria nativa,
  • mapear estructuras,
  • trabajar con punteros,
  • invocar funciones dinámicamente.


Todo usando una API moderna.


La FFM API tiene dos pilares:

1. Foreign Function


Permite invocar funciones nativas.


Por ejemplo:

  • printf
  • strlen
  • malloc
  • funciones de librerías C


2. Foreign Memory

Permite manejar memoria fuera del heap de Java.


Esto es importante porque:

  • la JVM normalmente controla toda la memoria,
  • pero muchas librerías nativas usan memoria propia.


La FFM API permite trabajar con esa memoria de manera segura.


Primer ejemplo: llamar a printf


import java.lang.foreign.*;

import java.lang.invoke.MethodHandle;

import static java.lang.foreign.ValueLayout.*;


public class Main {


    public static void main(String[] args) throws Throwable {

        Linker linker = Linker.nativeLinker();

        SymbolLookup stdlib = linker.defaultLookup();


        MemorySegment printfAddress =

                stdlib.find("printf")

                      .orElseThrow();


        FunctionDescriptor printfSignature =

                FunctionDescriptor.of(JAVA_INT, ADDRESS);


        MethodHandle printf = linker.downcallHandle(

                printfAddress,

                printfSignature

        );


        try (Arena arena = Arena.ofConfined()) {

            MemorySegment text =

                    arena.allocateFrom("Hola desde C!\n");


            printf.invoke(text);

        }

    }

}


¿Qué está pasando acá?


Linker linker = Linker.nativeLinker();

Obtiene un linker capaz de interactuar con funciones nativas.


SymbolLookup stdlib = linker.defaultLookup();

Busca símbolos exportados por librerías nativas.


stdlib.find("printf")

Obtiene la dirección de memoria de la función.

Muy parecido a: void* ptr = dlsym(...)


FunctionDescriptor.of(JAVA_INT, ADDRESS)

Describe la firma de la función.

En este caso: int printf(char*)


linker.downcallHandle(...)

Crea un MethodHandle que permite invocar la función nativa.


Manejo de memoria con Arena

Uno de los conceptos más importantes es:

Un Arena administra memoria nativa.


try (Arena arena = Arena.ofConfined()) {


}


Cuando el bloque termina:

  • la memoria se libera automáticamente.
  • Esto evita muchísimos memory leaks.


La memoria nativa se representa con: MemorySegment


Es básicamente:

  • un bloque de memoria,
  • con límites,
  • seguro,
  • y controlado.


Y para reservar memoria hacemos: 

MemorySegment segment = arena.allocate(4);

Por ejemplo escribir y leer un entero: 

segment.set(JAVA_INT, 0, 42);

int value = segment.get(JAVA_INT, 0);


Supongamos esta estructura:

struct Point {

    int x;

    int y;

};


Podemos modelarla en Java.

StructLayout POINT = MemoryLayout.structLayout(

        JAVA_INT.withName("x"),

        JAVA_INT.withName("y")

);


Y para reservar memoria hacemos:

MemorySegment point = arena.allocate(POINT);


Escribir los campos:


point.set(JAVA_INT, 0, 10);

point.set(JAVA_INT, 4, 20);


¿Qué ventajas tiene?

Menos complejidad


No hace falta:

  • generar headers,
  • compilar JNI,
  • usar glue code.


¿Puede FFM API reemplaza completamente JNI?

Todavía no en todos los casos.

Hay escenarios avanzados donde JNI sigue siendo necesario.


Pero para muchísimos casos:

  • bindings simples,
  • acceso a librerías,
  • llamadas nativas,
  • estructuras,


la FFM API es mucho mejor.