Java advanced usage: Memory and Pointer in JNA

Get into the habit of writing together! This is the 9th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the event details .

Introduction

We know that there are many pointers in the native code, and these pointers are mapped to Pointer in JNA. In addition to Pointer, JNA also provides a more powerful Memory class. This article will discuss the use of Pointer and Memory in JNA.

Pointer

Pointer is a class introduced in JNA to represent pointers in native methods. Recall that what is the pointer in the native method?

The pointer in the native method is actually an address, which is the memory address of the real object. So a peer property is defined in Pointer to store the memory address of the real object:

protected long peer;
复制代码

In real time, the constructor of Pointer needs to pass in this peer parameter:

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

Next, let's take a look at how to get a real object from Pointer. Here is an example of a byte array:

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

In fact, this method calls the Native.read method, let's continue to look at this read method:

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

You can see that it is a real native method to read a pointer object.

In addition to Byte arrays, Pointer also provides many other types of reading methods.

Read and write again, let's look at how Pointer writes data:

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

Similarly, the Native.write method is called to write data.

Here the Native.write method is also a native method:

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

Pointer also provides writing methods for many other types of data.

Of course, there are more direct get* methods:

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

Special Pointer: Opaque

In Pointer, there are also two createConstant methods, which are used to create a Pointer that is neither readable nor writable:

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

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

In fact, what is returned is the Opaque class, which inherits from Pointer, but all read or write methods in it will throw 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);
        }
复制代码

Memory

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对象。

Finally, the corresponding read and write methods are also provided in Memory, but the methods in Memory are different from Pointer. The methods in Memory have an additional boundsCheck, as shown below:

    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);
    }
复制代码

Why is there boundsCheck? This is because Memory is different from Pointer, and there is a size attribute in Memory, which is used to store the allocated memory size. The use of boundsCheck is to determine whether the accessed address is out of bounds to ensure the security of the program.

Summarize

Pointer and Memory are advanced functions in JNA. If you want to map with the native alloc method, you should consider using them.

This article has been published on www.flydean.com/06-jna-memo…

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!

Guess you like

Origin juejin.im/post/7084578611507757063