JEP 412: передать массив байтов в куче в собственный код, получив исключение UnsupportedOperationException: не собственный адрес
Все это с новым 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()
а затем скопируйте в него байтовый массив, но я думаю, что этого следует избегать.
Есть идеи по этому поводу? Я что-то делаю неправильно или не понимаю, как использовать память в куче?
Спасибо. Я могу опубликовать где-нибудь готовый к сборке и запускаемый проект, если это будет полезно.
1 ответ
Я думал, что одним из основных преимуществ Project Panama будет то, что нативный код может получить доступ к памяти в куче, чтобы избежать копирования.
На самом деле, несмотря на значительный прогресс в удобстве использования с проектом Panama, это невозможно по нескольким причинам.
- GC перемещает вещи в памяти кучи Java, поэтому адрес любого объекта (включая таблицы) может/изменится со временем. Однако собственному коду дается указатель на адрес памяти, который, конечно, не будет обновляться после цикла GC (не говоря уже о доступе к этой памяти в середине цикла).
- У JNI были API-интерфейсы, которые фактически предотвращали сбор мусора в середине нативного кода через
Get*Critical
разделы. К сожалению, предотвращение сборщика мусора может существенно повлиять на производительность приложения.
На самом деле Project Panama как раз и пытается избежать блокировки GC. Вот почему есть четкое разделение памяти, к которой осуществляется доступ, и почему необходимо копировать в / из родной памяти.
Это не должно быть большой проблемой, если только это не путь к горячему коду (т. е. он вызывается очень-очень часто) или код имеет дело с очень большими данными. В таком случае код может захотеть выполнить большую часть работы вне кучи. Если данные находятся в файле, возможно, получите доступ к этому файлу из собственного кода или используйте файл с отображением памяти панамы.
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());
}