JEP 412:将堆上字节数组传递给本机代码,得到 UnsupportedOperationException:不是本机地址
JEP 412: Pass a on-heap byte array to native code, getting UnsupportedOperationException: Not a native address
所有这些都是新 JDK 17.
我正在尝试将堆上字节数组转换为 MemorySegment 并将其传递给本机函数。我创建了简单的示例代码来展示这一点:
final CLinker cLinker = CLinker.getInstance();
// int strlen(const char *str);
final Optional<MemoryAddress> oSymbolAddress = CLinker.systemLookup().lookup("strlen");
final MethodHandle mh = cLinker.downcallHandle(oSymbolAddress.get(),
MethodType.methodType(int.class, MemoryAddress.class),
FunctionDescriptor.of(C_INT, C_POINTER));
out.println("I found this method handle: " + mh);
final byte[] ba = new byte[100];
ba[0] = 'h';
ba[1] = 'e';
ba[2] = 'l';
ba[3] = 'l';
ba[4] = 'o';
ba[5] = 0;
final MemorySegment stringSegment = MemorySegment.ofArray(ba);
final int result = (Integer) mh.invoke(stringSegment.address());
out.println("The length of the string is: " + result);
它尝试 运行 但抛出:
Exception in thread "main" java.lang.UnsupportedOperationException: Not a native address
at jdk.incubator.foreign/jdk.internal.foreign.MemoryAddressImpl.toRawLongValue(MemoryAddressImpl.java:91)
如果不使用 MemorySegment.ofArray(ba)
,我使用这个:
final MemorySegment stringSegment = MemorySegment.allocateNative(100, newImplicitScope());
stringSegment.asByteBuffer().put(ba);
它有效并给出了预期的答案 (5)。
我在MemoryAddressImpl.java
中查找了这个函数,我可以看到:
@Override
public long toRawLongValue() {
if (segment != null) {
if (segment.base() != null) {
throw new UnsupportedOperationException("Not a native address");
}
segment.checkValidState();
}
return offset();
}
显然 segment.base()
正在返回 null。
我不太明白这是怎么回事。我认为 Project Panama 的主要优势之一是本机代码可以访问堆内存,以避免必须复制。我当然可以做一个 allocateNative()
,然后将字节数组复制到其中,但我认为应该避免这种复制。
对此有什么想法吗?我是做错了什么还是误解了如何使用堆内存?
I thought that one of the major advantages of Project Panama would be that native code could access on-heap memory, to avoid having to copy.
实际上,尽管巴拿马项目在可用性方面取得了重大进展,但由于多种原因,这是不可能的。
- GC 在 Java 堆内存中四处移动,因此任何对象(包括表)的地址 may/will 都会随时间变化。然而,本机代码被赋予一个指向内存地址的指针,该地址在 GC 循环后当然不会更新(甚至没有提到在循环中间访问该内存)。
- JNI 有 API 可以实际防止 GC 在本机代码中间通过
Get*Critical
部分发生。不幸的是,阻止 GC 可能会对应用程序性能产生重大影响。
事实上,巴拿马项目正试图避免阻塞 GC。这就是为什么访问的内存有明确的分离以及为什么有必要复制 to/from 本机内存。
这应该不是什么大问题,除非这是热代码路径(即它被非常频繁地调用),或者代码处理非常大的数据。在这种情况下,代码可能希望在堆外完成大部分工作。如果数据在文件中,可以从本机代码访问该文件,或使用巴拿马内存映射文件。
var big = Path.of("path/to/big.data");
try (var scope = ResourceScope.newConfinedScope()) {
var bigMM = MemorySegment.mapFile(big, 0, Files.size(big), FileChannel.MapMode.READ_ONLY, scope);
return (int) mh.invoke(bigMM.address());
}
所有这些都是新 JDK 17.
我正在尝试将堆上字节数组转换为 MemorySegment 并将其传递给本机函数。我创建了简单的示例代码来展示这一点:
final CLinker cLinker = CLinker.getInstance();
// int strlen(const char *str);
final Optional<MemoryAddress> oSymbolAddress = CLinker.systemLookup().lookup("strlen");
final MethodHandle mh = cLinker.downcallHandle(oSymbolAddress.get(),
MethodType.methodType(int.class, MemoryAddress.class),
FunctionDescriptor.of(C_INT, C_POINTER));
out.println("I found this method handle: " + mh);
final byte[] ba = new byte[100];
ba[0] = 'h';
ba[1] = 'e';
ba[2] = 'l';
ba[3] = 'l';
ba[4] = 'o';
ba[5] = 0;
final MemorySegment stringSegment = MemorySegment.ofArray(ba);
final int result = (Integer) mh.invoke(stringSegment.address());
out.println("The length of the string is: " + result);
它尝试 运行 但抛出:
Exception in thread "main" java.lang.UnsupportedOperationException: Not a native address
at jdk.incubator.foreign/jdk.internal.foreign.MemoryAddressImpl.toRawLongValue(MemoryAddressImpl.java:91)
如果不使用 MemorySegment.ofArray(ba)
,我使用这个:
final MemorySegment stringSegment = MemorySegment.allocateNative(100, newImplicitScope());
stringSegment.asByteBuffer().put(ba);
它有效并给出了预期的答案 (5)。
我在MemoryAddressImpl.java
中查找了这个函数,我可以看到:
@Override
public long toRawLongValue() {
if (segment != null) {
if (segment.base() != null) {
throw new UnsupportedOperationException("Not a native address");
}
segment.checkValidState();
}
return offset();
}
显然 segment.base()
正在返回 null。
我不太明白这是怎么回事。我认为 Project Panama 的主要优势之一是本机代码可以访问堆内存,以避免必须复制。我当然可以做一个 allocateNative()
,然后将字节数组复制到其中,但我认为应该避免这种复制。
对此有什么想法吗?我是做错了什么还是误解了如何使用堆内存?
I thought that one of the major advantages of Project Panama would be that native code could access on-heap memory, to avoid having to copy.
实际上,尽管巴拿马项目在可用性方面取得了重大进展,但由于多种原因,这是不可能的。
- GC 在 Java 堆内存中四处移动,因此任何对象(包括表)的地址 may/will 都会随时间变化。然而,本机代码被赋予一个指向内存地址的指针,该地址在 GC 循环后当然不会更新(甚至没有提到在循环中间访问该内存)。
- JNI 有 API 可以实际防止 GC 在本机代码中间通过
Get*Critical
部分发生。不幸的是,阻止 GC 可能会对应用程序性能产生重大影响。
事实上,巴拿马项目正试图避免阻塞 GC。这就是为什么访问的内存有明确的分离以及为什么有必要复制 to/from 本机内存。
这应该不是什么大问题,除非这是热代码路径(即它被非常频繁地调用),或者代码处理非常大的数据。在这种情况下,代码可能希望在堆外完成大部分工作。如果数据在文件中,可以从本机代码访问该文件,或使用巴拿马内存映射文件。
var big = Path.of("path/to/big.data");
try (var scope = ResourceScope.newConfinedScope()) {
var bigMM = MemorySegment.mapFile(big, 0, Files.size(big), FileChannel.MapMode.READ_ONLY, scope);
return (int) mh.invoke(bigMM.address());
}