Ответ
Pinned Object Heap (POH) — это специальный регион управляемой кучи, введённый в .NET 5, предназначенный для хранения объектов, которые должны быть закреплены (pinned) в памяти. Закрепление предотвращает перемещение объекта сборщиком мусора (GC), что необходимо при передаче указателя на управляемую память в неуправляемый код (например, через P/Invoke или при работе с IO).
Проблема, которую решает POH:
Раньше для закрепления использовался оператор fixed или GCHandle. Это приводило к:
- Фрагментации кучи: Закреплённый объект в обычной куче (
SOH/LOH) блокировал компактизацию GC, создавая "дыры". - Накладным расходам: Процесс закрепления/открепления требовал дополнительных операций.
Как работает POH:
- Объекты, выделенные в POH, никогда не перемещаются GC.
- Это позволяет передавать их указатели в нативный код напрямую, без дополнительного закрепления.
- POH не компактируется, что исключает фрагментацию основной кучи из-за pinned-объектов.
Практическое использование:
using System;
using System.Runtime.InteropServices;
// Пример нативного метода, работающего с буфером
[DllImport("NativeLib.dll")]
private static extern int ProcessBuffer(byte[] buffer, int length);
public void WorkWithPinnedBuffer()
{
// 1. Выделяем буфер в Pinned Object Heap (pinned: true)
// Размер 4096 байт, массив считается "долгоживущим" и закреплённым.
byte[] buffer = GC.AllocateArray<byte>(length: 4096, pinned: true);
// 2. Заполняем буфер данными (например, из файла или сети)
new Random().NextBytes(buffer);
// 3. Передаём в неуправляемый код. GC не будет двигать массив,
// поэтому указатель остаётся валидным на всё время вызова.
int result = ProcessBuffer(buffer, buffer.Length);
Console.WriteLine($"Native code processed buffer, result: {result}");
// 4. Важно: память в POH освобождается только при сборке мусора
// поколения 2 или при выгрузке домена. Явно "удалить" объект нельзя.
buffer = null; // Делаем объект доступным для GC
// Рекомендуется для долгоживущих или крупных pinned-буферов.
}
Ключевые моменты и рекомендации:
- Когда использовать: Для буферов, которые часто передаются в нативный код (работа с файлами, сетевыми пакетами, графическими API).
- Выделение: Только через
GC.AllocateArray<T>(int length, bool pinned). Обычныйnewне размещает объекты в POH. - Очистка: Память в POH не освобождается сразу, когда объект становится недостижим. Она освобождается при полной сборке мусора (Gen 2). Не используйте POH для короткоживущих объектов.
- Наблюдение: За POH можно следить через счетчики производительности .NET или события
GC.GetGCMemoryInfo().PinnedObjectsMemorySize.