11

java高级用法之:JNA中的Memory和Pointer_Java_程序那些事_InfoQ写作平台

 2 years ago
source link: https://xie.infoq.cn/article/72067c313bb6fc79986a7e890
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们知道在 native 的代码中有很多指针,这些指针在 JNA 中被映射成为 Pointer。除了 Pointer 之外,JNA 还提供了更加强大的 Memory 类,本文将会一起探讨 JNA 中的 Pointer 和 Memory 的使用。

Pointer

Pointer 是 JNA 中引入的类,用来表示 native 方法中的指针。大家回想一下 native 方法中的指针到底是什么呢?

native 方法中的指针实际上就是一个地址,这个地址就是真正对象的内存地址。所以在 Pointer 中定义了一个 peer 属性,用来存储真正对象的内存地址:

protected long peer;

实时上,Pointer 的构造函数就需要传入这个 peer 参数:

public Pointer(long peer) {        this.peer = peer;    }

接下来我们看一下如何从 Pointer 中取出一个真正的对象,这里以 byte 数组为例:

    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);

可以看到它是一个真正的 native 方法,用来读取一个指针对象。

除了 Byte 数组之外,Pointer 还提供了很多其他类型的读取方法。

又读取就有写入,我们再看下 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 方法也是一个 native 方法:

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

Pointer 还提供了很多其他类型数据的写入方法。

当然还有更加直接的 get*方法:

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

特殊的 Pointer:Opaque

在 Pointer 中,还有两个 createConstant 方法,用来创建不可读也不可写的 Pointer:

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

实际上返回的而是 Opaque 类,这个类继承自 Pointer,但是它里面的所有 read 或者 write 方法,都会抛出 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 对象。

最后 Memory 中也提供了对应的 read 和 write 方法,但是 Memory 中的方法和 Pointer 不同,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 呢?这是因为 Memory 和 Pointer 不同,Memory 中有一个 size 的属性,用来存储分配的内存大小。使用 boundsCheck 就是来判断访问的地址是否出界,用来保证程序的安全。

Pointer 和 Memory 算是 JNA 中的高级功能,大家如果想要和 native 的 alloc 方法进行映射的话,就要考虑使用了。

本文已收录于 http://www.flydean.com/06-jna-memory/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK