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

11次阅读

共计 4290 个字符,预计需要花费 11 分钟才能阅读完成。

简介

咱们晓得在 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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

正文完
 0