简介
我们知道在 native 的代码中有很多指针,这些指针在 JNA 中被映射成为 Pointer。除了 Pointer 之外,JNA 还提供了更加强大的 Memory 类,本文将会一起探讨 JNA 中的 Pointer 和 Memory 的使用。
Pointer
Pointer 是 JNA 中引入的类,用来表示 native 方法中的指针。大家回想一下 native 方法中的指针到底是什么呢?
native 方法中的指针实际上就是一个地址,这个地址就是真正对象的内存地址。所以在 Pointer 中定义了一个 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/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
评论