jdk16 foreign包

學習jdk16的jdk.incubator.foreign包中api,這個包仍是在incubator下,自己不打算寫啥東西,可是遇到了一個頗有意思的用法,這裏記錄一下。java

這個有相似jni,jna的功能,能夠調用C函數。這個是jdk16的新方式,調用C函數時,能夠傳遞基礎類型,也能夠傳遞地址。api

當傳遞指針時,須要先建立MemorySegment,而後調用address()方法獲取地址,這個地址應該是實際內存地址。數組

就好比傳遞int數組,建立MemorySegment,分配空間,填充數據jvm

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代碼:spa

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源碼寫的,測試沒問題。image.png指針