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