写点什么

java 高级用法之:JNA 中的 Memory 和 Pointer

作者:程序那些事
  • 2022 年 4 月 14 日
  • 本文字数:3189 字

    阅读完需:约 10 分钟

java高级用法之:JNA中的Memory和Pointer

简介

我们知道在 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/

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

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

发布于: 2022 年 04 月 14 日阅读数: 35
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
java高级用法之:JNA中的Memory和Pointer_Java_程序那些事_InfoQ写作平台