Javaの高度な使用法:JNAのメモリとポインタ

一緒に書く習慣をつけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して9日目です。クリックしてイベントの詳細をご覧ください

序章

ネイティブコードには多くのポインターがあり、これらのポインターはJNAのポインターにマップされていることがわかっています。ポインタに加えて、JNAはより強力なメモリクラスも提供します。この記事では、JNAでのポインタとメモリの使用について説明します。

ポインタ

ポインタは、ネイティブメソッドのポインタを表すためにJNAで導入されたクラスです。ネイティブメソッドのポインタは何であるかを思い出してください。

ネイティブメソッドのポインタは実際にはアドレスであり、これは実際のオブジェクトのメモリアドレスです。したがって、実際のオブジェクトのメモリアドレスを格納するために、ポインタでピアプロパティが定義されます。

protected long peer;
复制代码

リアルタイムで、Pointerのコンストラクターはこのピアパラメーターを渡す必要があります。

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

次に、ポインタから実際のオブジェクトを取得する方法を見てみましょう。バイト配列の例を次に示します。

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

実際、このメソッドはNative.readメソッドを呼び出します。引き続き、このreadメソッドを見てみましょう。

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

ポインタオブジェクトを読み取るのは実際のネイティブメソッドであることがわかります。

バイト配列に加えて、ポインタは他の多くのタイプの読み取り方法も提供します。

もう一度読み取りと書き込みを行い、Pointerがデータを書き込む方法を見てみましょう。

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

同様に、Native.writeメソッドが呼び出されてデータが書き込まれます。

ここで、Native.writeメソッドはネイティブメソッドでもあります。

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

ポインタは、他の多くのタイプのデータの書き込みメソッドも提供します。

もちろん、もっと直接的なget*メソッドがあります。

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

特別なポインタ:不透明

ポインターには、読み取りも書き込みもできないポインターを作成するために使用される2つのcreateConstantメソッドもあります。

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

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

実際、Pointerから継承するOpaqueクラスが返されますが、その中のすべての読み取りまたは書き込みメソッドは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);
        }
复制代码

メモリー

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

最後に、対応する読み取りおよび書き込みメソッドもメモリで提供されますが、メモリ内のメソッドはポインタとは異なります。メモリ内のメソッドには、以下に示すように、追加のboundsCheckがあります。

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

なぜboundsCheckがあるのですか?これは、メモリがポインタとは異なり、割り当てられたメモリサイズを格納するために使用されるサイズ属性がメモリにあるためです。boundsCheckの使用は、プログラムのセキュリティを確保するために、アクセスされたアドレスが範囲外であるかどうかを判断することです。

要約する

ポインタとメモリはJNAの高度な機能であり、ネイティブのallocメソッドを使用してマップする場合は、それらの使用を検討する必要があります。

この記事はwww.flydean.com/06-jna-memoに掲載されました…

最も人気のある解釈、最も深遠な乾物、最も簡潔なチュートリアル、そしてあなたが知らない多くのトリックがあなたが発見するのを待っています!

私の公式アカウントに注意を払うことを歓迎します:「それらをプログラムする」、テクノロジーを理解し、あなたをよりよく理解する!

おすすめ

転載: juejin.im/post/7084578611507757063