Uso avanzado de Java: memoria y puntero en JNA

¡Acostúmbrate a escribir juntos! Este es el noveno día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

Introducción

Sabemos que hay muchos punteros en el código nativo y estos punteros están asignados a Pointer en JNA. Además de Pointer, JNA también proporciona una clase de memoria más poderosa.Este artículo discutirá el uso de Pointer y Memory en JNA.

Puntero

Pointer es una clase introducida en JNA para representar punteros en métodos nativos. Recuerde que ¿qué es el puntero en el método nativo?

El puntero en el método nativo es en realidad una dirección, que es la dirección de memoria del objeto real. Entonces, se define una propiedad de pares en Pointer para almacenar la dirección de memoria del objeto real:

protected long peer;
复制代码

En tiempo real, el constructor de Pointer necesita pasar este parámetro del par:

public Pointer(long peer) {
        this.peer = peer;
    }
复制代码

A continuación, echemos un vistazo a cómo obtener un objeto real de Pointer. Aquí hay un ejemplo de una matriz de bytes:

    public void read(long offset, byte[] buf, int index, int length) {
        Native.read(this, this.peer, offset, buf, index, length);
    }
复制代码

De hecho, este método llama al método Native.read, sigamos analizando este método de lectura:

static native void read(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length);
复制代码

Puede ver que es un método nativo real para leer un objeto de puntero.

Además de las matrices de bytes, Pointer también proporciona muchos otros tipos de métodos de lectura.

Lea y escriba de nuevo, veamos cómo Pointer escribe datos:

    public void write(long offset, byte[] buf, int index, int length) {
        Native.write(this, this.peer, offset, buf, index, length);
    }
复制代码

De manera similar, se llama al método Native.write para escribir datos.

Aquí el método Native.write también es un método nativo:

static native void write(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length);
复制代码

Pointer también proporciona métodos de escritura para muchos otros tipos de datos.

Por supuesto, hay métodos get* más directos:

public byte getByte(long offset) {
        return Native.getByte(this, this.peer, offset);
    }
复制代码

Puntero especial: opaco

En Pointer, también hay dos métodos createConstant, que se utilizan para crear un puntero que no se puede leer ni escribir:

    public static final Pointer createConstant(long peer) {
        return new Opaque(peer);
    }

    public static final Pointer createConstant(int peer) {
        return new Opaque((long)peer & 0xFFFFFFFF);
    }
复制代码

De hecho, se devuelve la clase Opaque, que hereda de Pointer, pero todos los métodos de lectura o escritura arrojarán UnsupportedOperationException:

    private static class Opaque extends Pointer {
        private Opaque(long peer) { super(peer); }
        @Override
        public Pointer share(long offset, long size) {
            throw new UnsupportedOperationException(MSG);
        }
复制代码

Memoria

Pointer是基本的指针映射,如果对于通过使用native的malloc方法分配的内存空间而言,除了Pointer指针的开始位置之外,我们还需要知道分配的空间大小。所以一个简单的Pointer是不够用了。

这种情况下,我们就需要使用Memory。

Memory是一种特殊的Pointer, 它保存了分配出来的空间大小。我们来看一下Memory的定义和它里面包含的属性:

public class Memory extends Pointer {
...
    private static ReferenceQueue<Memory> QUEUE = new ReferenceQueue<Memory>();
    private static LinkedReference HEAD; // the head of the doubly linked list used for instance tracking
    private static final WeakMemoryHolder buffers = new WeakMemoryHolder();
    private final LinkedReference reference; // used to track the instance
    protected long size; // Size of the malloc'ed space
...
}
复制代码

Memory里面定义了5个数据,我们接下来一一进行介绍。

首先是最为重要的size,size表示的是Memory中内存空间的大小,我们来看下Memory的构造函数:

    public Memory(long size) {
        this.size = size;
        if (size <= 0) {
            throw new IllegalArgumentException("Allocation size must be greater than zero");
        }
        peer = malloc(size);
        if (peer == 0)
            throw new OutOfMemoryError("Cannot allocate " + size + " bytes");

        reference = LinkedReference.track(this);
    }
复制代码

可以看到Memory类型的数据需要传入一个size参数,表示Memory占用的空间大小。当然,这个size必须要大于0.

然后调用native方法的malloc方法来分配一个内存空间,返回的peer保存的是内存空间的开始地址。如果peer==0,表示分配失败。

如果分配成功,则将当前Memory保存到LinkedReference中,用来跟踪当前的位置。

我们可以看到Memory中有两个LinkedReference,一个是HEAD,一个是reference。

LinkedReference本身是一个WeakReference,weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

private static class LinkedReference extends WeakReference<Memory>
复制代码

我们看一下LinkedReference的构造函数:

private LinkedReference(Memory referent) {
            super(referent, QUEUE);
        }
复制代码

这个QUEUE是ReferenceQueue,表示的是GC待回收的对象列表。

我们看到Memory的构造函数除了设置size之外,还调用了:

reference = LinkedReference.track(this);
复制代码

仔细看LinkedReference.track方法:

   static LinkedReference track(Memory instance) {
            // use a different lock here to allow the finialzier to unlink elements too
            synchronized (QUEUE) {
                LinkedReference stale;

                // handle stale references here to avoid GC overheating when memory is limited
                while ((stale = (LinkedReference) QUEUE.poll()) != null) {
                    stale.unlink();
                }
            }

            // keep object allocation outside the syncronized block
            LinkedReference entry = new LinkedReference(instance);

            synchronized (LinkedReference.class) {
                if (HEAD != null) {
                    entry.next = HEAD;
                    HEAD = HEAD.prev = entry;
                } else {
                    HEAD = entry;
                }
            }

            return entry;
        }
复制代码

这个方法的意思是首先从QUEUE中拿出那些准备被垃圾回收的Memory对象,然后将其从LinkedReference中unlink。 最后将新创建的对象加入到LinkedReference中。

因为Memory中的QUEUE和HEAD都是类变量,所以这个LinkedReference保存的是JVM中所有的Memory对象。

Finalmente, los métodos de lectura y escritura correspondientes también se proporcionan en la memoria, pero los métodos en la memoria son diferentes de Pointer.Los métodos en la memoria tienen una comprobación de límites adicional, como se muestra a continuación:

    public void read(long bOff, byte[] buf, int index, int length) {
        boundsCheck(bOff, length * 1L);
        super.read(bOff, buf, index, length);
    }

    public void write(long bOff, byte[] buf, int index, int length) {
        boundsCheck(bOff, length * 1L);
        super.write(bOff, buf, index, length);
    }
复制代码

¿Por qué hay límites? Esto se debe a que la memoria es diferente del puntero y hay un atributo de tamaño en la memoria, que se usa para almacenar el tamaño de la memoria asignada. El uso deboundsCheck es determinar si la dirección a la que se accede está fuera de los límites para garantizar la seguridad del programa.

Resumir

Pointer y Memory son funciones avanzadas en JNA. Si desea mapear con el método de asignación nativo, debería considerar usarlas.

Este artículo ha sido publicado en www.flydean.com/06-jna-memo…

¡La interpretación más popular, los productos secos más profundos, los tutoriales más concisos y muchos trucos que no conoces están esperando que los descubras!

Bienvenido a prestar atención a mi cuenta oficial: "Programe esas cosas", entienda la tecnología, ¡entiéndalo mejor!

Supongo que te gusta

Origin juejin.im/post/7084578611507757063
Recomendado
Clasificación