+-
jdk16 foreign包
首页 专栏 java 文章详情
1

jdk16 foreign包

gg22g2 发布于 5 月 16 日

学习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源码写的,测试没问题。

java c
阅读 68 更新于 5 月 16 日
举报
赞1 收藏
分享
本作品系原创, 采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar
gg22g2
34 声望
0 粉丝
关注作者
0 条评论
得票数 最新
提交评论
avatar
gg22g2
34 声望
0 粉丝
关注作者
宣传栏
目录

学习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源码写的,测试没问题。