Одним из ключевых компонентов при взаимодействии с нативными (внешними) функциями является арена (arena).
При обращении к нативным (внешним) функциями код Java должен работать с памятью вне кучи (хипа), то есть с памятью, которая не была выделена с помощью оператора new.
И арена как раз управляет жизненным циклом сегментов памяти вне кучи, позволяя легко выделять и автоматически освобождать используемую память.
На платформе Java арена представлена классом Arena. Для создания арены применяется один из статических методов класса:
global(): возвращает "глобальную" арену. Сегменты памяти, выделенные в рамках глобальной арены, доступны любому потоку.
ofAuto(): cоздает новую арену, которая автоматически управляется сборщиком мусора. Сегменты арены доступны любому потоку
ofConfined(): возвращает новую "ограниченную" арену. Сегменты, выделенные для ограниченной арены, доступны потоку, который создал арену, то есть потоку-владельцу арены.
ofShared(): возвращает новую общую арену. Сегменты, выделенные для общей арены, доступны любому потоку.
Для закрытия арены предназначен метод close(). Но в зависимости от типа арены принцип закрытия может различаться:
"Ограниченная" арена (ofConfined()) должна быть закрыта в том же потоке, в котором она была создана. Иначе будет выброшено исключение.
"Глобальная" арена (global()) никогда не закрывается, и выделенные ею сегменты памяти никогда не освобождаются.
"Автоматическую" арену (ofAuto()) закрыть нельзя. Сборщик мусора сам в конечном итоге освободит сегменты памяти, когда они, как и арена, перестанут использоваться. Это удобно, но менее предсказуемо.
"Общая" арена (ofShared()) работает до тех пор, пока не будет вызван метод close в любом потоке
Таким образом, явно вызывать метод close() необходимо только для ограниченной и общей арен, а для глобальной и автоматической вызов метода close() сгенерирует исключение.
После успешного выполнения метода close() доступ ко всем связанным с ареной сегментам памяти станет невозможным, а область памяти вне кучи, связанная с ареной, будет освобождена.:
Arena arena = Arena.ofConfined(); // создаем арену // неокторая работа с ареной arena.close(); // закрываем арену
Но поскольку класс арены реализует интерфейс AutoCloseable (в виде метода close()), то арену удобно создавать и использовать через конструкцию try-с ресурсами, которая автоматически
освобождает память арены:
try (Arena arena = Arena.ofConfined()) {
// работа с ареной
} // Арена закрыта
Сегмент памяти представляет непрерывный блок памяти. На уровне кода Java сегмент памяти представлен интерфейсом MemorySegment. Сегмент памяти может находиться в куче Java, управляемой сборщиком мусора, или быть выделен вне кучи Java. Сегмент памяти вне кучи имеет фиксированный физический адрес. Сегмент кучи, как и любой объект Java, имеет ссылку, по которой его можно найти, но не имеет фиксированного адреса, поскольку сборщик мусора мог его переместить.
Для использования с внешними/нативными функциями выделяются сегменты памяти вне кучи.
Для выделения памяти у класса Arena есть ряд методов (в основном реализаций интерфейса java.lang.foreign.SegmentAllocator).
Некоторые из них:
allocate(long byteSize, long byteAlignment)
Этот метод принимает два параметра:
byteSize: размер выделяемой памяти в байтах
byteAlignment: выравнивание памяти в байтах
Метод возвращает сегмент памяти заданного размера (первый параметр), адрес которого выравнивается исходя из значения второго параметра.
allocateFrom(String str)
Преобразует строку Java в строку C, завершающуюся нулевым символом, используя кодировку UTF-8, и сохраняет результат в сегмент памяти.
allocateFrom(ValueLayout.ofType elementLayout, Type[] elements)
Выделяет память для хранения элементов заданного примитивного типа Type (это может быть int, float и т.д.).
allocateFrom(ValueLayout.of[Type] elementLayout, Type element)
Аналогичен предыдущему варианту, только выделяет память для хранения одного элемента element заданного примитивного типа Type и сохраняет значение element в выделенной памяти
allocate(MemoryLayout layout)
Выделяет память вне кучи с заданным макетом layout.
allocate(MemoryLayout layout, long count)
Выделяет память вне кучи с заданным макетом layout с количеством элементов равным count
Все эти методы возвращают объект типа MemorySegment. Пример применения метода allocate():
import java.lang.foreign.*;
public class Program {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
// выделяем сегмент памяти - в 16 байт с выравниванием по 8 байтам
MemorySegment segment1 = arena.allocate(16, 8);
// можем выделить еще один сегмент
MemorySegment segment2 = arena.allocate(32, 4);
// просто выведем информацию на экран
System.out.println(segment1); // MemorySegment{ kind: native, address: 0x772c881780e0, byteSize: 16 }
System.out.println(segment2); // MemorySegment{ kind: native, address: 0x772c881783d0, byteSize: 32 }
}
}
}
Метод allocateFrom() является наиболее простым способом для сохранения в сегменте памяти вне кучи какой-нибудь строки:
import java.lang.foreign.*;
public class Program {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
String str = "Hello METANIT.COM";
MemorySegment str_seg = arena.allocateFrom(str);
System.out.println(str_seg); // MemorySegment{ kind: native, address: 0x7aa79c1802d0, byteSize: 18 }
}
}
}
Здесь происходит кодирование строки Java в UTF-8 и выделение сегмент памяти, который содержит байты строки, включая нулевой терминатор.
Для освобождения сегмента памяти в ограниченной или общей арене необходимо закрыть эту арену с помощью метода close().
В автоматической арене для освобождения сегмента сегменту передается значение null, и затем в какой-то момент времени сборщик мусора освободит память:
MemorySegment segment = Arena.ofAuto().allocate(...); ... segment = null; // Память в конечном итоге освобождается
Наконец, сегменты памяти глобальной арены в процессе работы программы никогда не освобождаются.
Стоит также отметить, что освободить отдельные сегменты памяти невозможно.