当前位置: 首页 > 后端技术 > Java

Java进阶用法:JNA中Memory和Pointer的介绍

时间:2023-04-01 17:57:42 Java

我们知道在native代码中有很多指针,而这些指针在JNA中都映射为Pointer。除了Pointer,JNA还提供了一个更强大的Memory类。本文将讨论JNA中Pointer和Memory的使用。PointerPointer是JNA中引入的一个类,用于表示native方法中的指针。我们回想一下native方法中的指针是什么?native方法中的指针其实就是一个地址,这个地址就是真正对象的内存地址。所以在Pointer中定义了一个peer属性,用来存放真实对象的内存地址:protectedlongpeer;实际上,Pointer的构造函数需要传入这个peer参数:publicPointer(longpeer){this.peer=peer;接下来,让我们看看如何从Pointer中获取一个真实的对象。这里我们以字节数组为例:}其实这个方法调用的是Native.read方法,我们继续看这个read方法:staticnativevoidread(Pointerpointer,longbaseaddr,longoffset,byte[]buf,intindex,intlength);你可以看到它是一个真正的读取指针对象的本地方法。除了Byte数组,Pointer还提供了很多其他类型的读取方法。读和写,看看Pointer是怎么写数据的:}同样调用Native.write方法写入数据。这里的Native.write方法也是一个native方法:staticnativevoidwrite(Pointerpointer,longbaseaddr,longoffset,byte[]buf,intindex,intlength);指针还提供了许多其他类型的数据写入方法。当然还有更直接的get*方法:publicbytegetByte(longoffset){returnNative.getByte(this,this.peer,offset);}特殊指针:Opaque在Pointer中,有两个createConstant方法,使用来创建一个不可读不可写的Pointer:publicstaticfinalPointercreateConstant(longpeer){returnnewOpaque(peer);}publicstaticfinalPointercreateConstant(intpeer){returnnewOpaque((long)peer&0xFFFFFFFF);}实际上返回的是Opaque类,它继承自Pointer,但是里面所有的读写方法都会抛出UnsupportedOperationException:}@OverridepublicPointershare(longoffset,longsize){thrownewUnsupportedOperationException(MSG);}MemoryPointer是一个基本的指针映射,如果对于使用nativemalloc方法分配的内存空间,除了Pointer指针的起始位置之外,我们还需要知道分配空间的大小。所以一个简单的指针是不够的。在这种情况下,我们需要使用内存。内存是一种特殊的指针,它保存分配的空间大小。我们来看看Memory的定义以及它包含的属性:publicclassMemoryextendsPointer{...私有静态LinkedReference头;//用于实例跟踪的双向链表的头部privatestaticfinalWeakMemoryHolderbuffers=newWeakMemoryHolder();私人最终LinkedReference参考;//用于跟踪实例保护的长尺寸;//sizeofthemalloc'edspace...}DefinedinMemory收集了5条数据,我们将一一介绍。首先是最重要的size,size表示Memory中内存空间的大小,我们看一下Memory的构造函数:publicMemory(longsize){this.size=size;if(size<=0){thrownewIllegalArgumentException("分配大小必须大于零");}peer=malloc(size);if(peer==0)thrownewOutOfMemoryError("Cannotallocate"+size+"bytes");reference=LinkedReference.track(这个);可以看到Memory类型的数据需要传入一个size参数,表示Memory占用的空间大小。当然这个size必须大于0。然后调用native方法的malloc方法分配一块内存空间,返回的peer保存内存空间的起始地址。如果peer==0,表示分配失败。如果分配成功,将当前Memory保存到LinkedReference中,用于跟踪当前位置。我们可以看到Memory中有两个LinkedReferences,一个是HEAD,一个是reference。LinkedReference本身是一个WeakReference,weekReference引用的对象只要进行垃圾回收就会被回收,不管内存是否不足。privatestaticclassLinkedReferenceextendsWeakReference我们来看看LinkedReference的构造函数:privateLinkedReference(Memoryreferent){super(referent,QUEUE);}这个QUEUE就是ReferenceQueue,代表GC要回收的对象列表。我们看到Memory的构造函数去掉了设置size之外,还调用了:reference=LinkedReference.track(this);仔仔细看LinkedReference.track方法:staticLinkedReferencetrack(Memoryinstance){//在这里使用不同的锁来允许取消链接太同步的元素的终结者(队列){LinkedReferencestale;//在这里处理陈旧的引用以避免内存有限时GC过热while((stale=(LinkedReference)QUEUE.poll())!=null){stale.unlink();}}//将对象分配保持在同步块之外LinkedReferenceentry=newLinkedReference(instance);synchronized(LinkedReference.class){if(HEAD!=null){entry.next=HEAD;HEAD=HEAD.prev=条目;}else{HEAD=条目;}}返回条目;}该方法的意思是先从QUEUE中取出准备进行垃圾回收的Memory对象,然后解除与LinkedReference的链接,最后将新创建的对象添加到LinkedReference中。因为Memory中的QUEUE和HEAD是类变量,所以这个LinkedReference保存了JVM中所有的Memory对象。最后,Memory也提供了相应的读写方法,只是Memory中的方法与Pointer不同。Memory中的方法有一个boundsCheck,如下所示:publicvoidread(longbOff,byte[]buf,intindex,intlength){boundsCheck(bOff,length*1L);super.read(bOff,buf,index,length);}publicvoidwrite(longbOff,byte[]buf,intindex,intlength){boundsCheck(bOff,length*1L);super.write(bOff,buf,index,length);}为什么会有boundsCheck?这是因为Memory不同于Pointer。Memory中有一个size属性,用来存储分配的内存大小。使用boundsCheck是为了判断访问的地址是否越界,保证程序的安全。综上所述,Pointer和Memory在JNA中算是高级函数。如果你想用原生的alloc方法映射,你应该考虑使用它们。本文已收录于http://www.flydean.com/06-jna-memory/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等着你去探索!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!