学习jdk16的jdk.incubator.foreign包中api,这个包还是在incubator下,自身不打算写啥货色,然而遇到了一个很有意思的用法,这里记录一下。

这个有相似jni,jna的性能,能够调用C函数。这个是jdk16的新形式,调用C函数时,能够传递根底类型,也能够传递地址。

当传递指针时,须要先创立MemorySegment,而后调用address()办法获取地址,这个地址应该是理论内存地址。

就比方传递int数组,创立MemorySegment,调配空间,填充数据

try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) {    for (int i = 0 ; i < 10 ; i++) {       MemoryAccess.setIntAtIndex(segment, i);    }}

所以我一开始传递byte[]的话,就须要先创立MemorySegment,而后将数据先拷贝到其中。我就在想,要是能获取到byte[]的理论内存地址,把这个地址传递过来是不是就能够缩小一次拷贝操作吗。

百度了一下,还真有获取java对象地址的办法,导入这个包,调用 VM.current().addressOf,就能获取理论物理地址了。

        <dependency>            <groupId>org.openjdk.jol</groupId>            <artifactId>jol-core</artifactId>            <version>0.15</version>        </dependency>                                import org.openjdk.jol.vm.VM;                byte[] b1 = new byte[24];        long address = VM.current().addressOf(b1);

当初获取到一个地址了,下边就是测试把这个地址传递给C函数,C函数从其中获取的内容是否合乎预期。
测试代码:
C定义的函数

int getIntFromPoint(void* p){    return ((int*)p)[0];}int getByteFromPoint(void* p) {    return ((unsigned char*)p)[0];}

java代码:

    public static void main(String[] args) {        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);        Path paths = Path.of("C:\\Users\\h6706\\source\\repos\\screenShot\\x64\\Release\\screenShot.dll");        MethodHandle getByteFromPoint = CLinker.getInstance().downcallHandle(                LibraryLookup.ofPath(paths).lookup("getByteFromPoint").get(),                MethodType.methodType(int.class, MemoryAddress.class),                FunctionDescriptor.of(CLinker.C_INT, CLinker.C_POINTER)        );        MethodHandle getIntFromPoint = CLinker.getInstance().downcallHandle(                LibraryLookup.ofPath(paths).lookup("getIntFromPoint").get(),                MethodType.methodType(int.class, MemoryAddress.class),                FunctionDescriptor.of(CLinker.C_INT, CLinker.C_POINTER)        );        byte[] b1 = new byte[24];        long address = VM.current().addressOf(b1);        try {            Field theInternalUnsafe1 = Unsafe.class.getDeclaredField("theInternalUnsafe");            theInternalUnsafe1.setAccessible(true);            jdk.internal.misc.Unsafe theInternalUnsafe = (jdk.internal.misc.Unsafe) theInternalUnsafe1.get(null);            long ARRAY_BASE_OFFSET = theInternalUnsafe.arrayBaseOffset(byte[].class);            //b[4]设置为3,这里尝试一下java间接操作内存            theInternalUnsafe.putByte(null, VM.current().addressOf(b1) + ARRAY_BASE_OFFSET + 4, (byte) 3);            try {                b1[0] = 8;                b1[3] = 1;                 // byte[]也是对象,他也有对象头, 8字节根本信息,4字节指针,4字节数组长度,所有ARRAY_BASE_OFFSET = 16                int result = (int) getByteFromPoint.invoke(new MemoryAddressImpl(null, address + ARRAY_BASE_OFFSET));                int result2 = (int) getIntFromPoint.invoke(new MemoryAddressImpl(null, address + ARRAY_BASE_OFFSET));                System.out.println(result);                System.out.println(result2);            } catch (Throwable throwable) {                throwable.printStackTrace();            }            System.out.println(Arrays.toString(b1));        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 这个获取地址的思路是,调用getInt,     * 获取array中偏移16字节地位的值,也就是从16地位开始取4字节,其实也就是获取到了Object[0]的值,必定是一个地址     * <p>     * 但这个地址在开启指针压缩的jvm中是还须要再放大8倍才是理论地址     */    public static long getAddress(jdk.internal.misc.Unsafe theInternalUnsafe, Object target) {        Object[] array = new Object[1];        array[0] = target;        //这里其实有两个状况,如果jvm开启压缩制作,地址只占有4字节,也能够调用getInt        long anInt = theInternalUnsafe.getLong(array, 16);        /**         * 如果开启指针压缩,则上一步获取的地址并不是理论地址,         * 指针压缩是,内存被jvm依照8字节(不是8比特)分块,anInt就代表是第几块内存         *         * 所以左移3次,放大八倍就能够取得到理论内存地址,当然这里也可能不是3,只是目前在我的电脑上查看是3         *         * 至于为啥还要加个0,这个我也不分明,但如同和调试无关         *         * */        anInt = 0 + (anInt << 3);        return anInt;    }

其中getAddress办法是依据VM.current().addressOf源码写的,测试没问题。