簡介
我們知道在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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!