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());
}
Другие вопросы по тегам