инструкция
При изучении Netty ByteBuf можно увидеть повсюду, но как эффективно выделить ByteBuf по-прежнему очень сложно. Распределение памяти в пуле Netty по-прежнему относительно сложно. Многие люди изучали и видели это, но это все еще туманно. в основном, чтобы объяснить: ** Netty выделяет детали объединенной памяти вне кучи, ** я с нетерпением жду, чтобы вы поняли! ! !
Картинки в статье для большей выразительности рисовал не менее 6 часов, рисунки были незнакомые, некоторые детали подчеркнуты.
Поскольку в исходном коде задействовано много бинарных операций, рекомендуется взглянуть на 2 бинарные статьи, которые я написал ранее:Java, связанный с двоичным кодом,Бинарные боевые навыки.
ByteBuf Важность
ByteBuf всегда существовал в Netty, и он необходим для чтения и записи!ByteBuf — это контейнер данных Netty, и эффективное размещение ByteBuf имеет решающее значение!
Netty читает данные из сокета.
Netty готова записать данные в сокет.Здесь мы видим, затем определите, будут ли данные будут записаны в сокет, если это стек, если не построить объект DirectBuffer, код детализации выглядит следующим образом:
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (buf.isDirect()) {
return msg;
}
return newDirectBuffer(buf);
}
Таким образом, эта статья в основном предназначена для объяснения: ** Netty выделяет детали объединенной памяти вне кучи, ** На самом деле, многие детали выделения памяти кучи схожи.
Примечание:Я не понимал, почему память вне кучи должна быть перенесена в память вне кучи.Я не понимал этого раньше.Внезапно у меня была дискуссия с г-ном Дишеном, и дискуссия и дискуссия прояснились.
Обзор
На этот раз основной разговор идет о распределении памяти в пуле., PooledByteBufAllocator — это запись операции для netty по выделению памяти в пуле.
Его предоставление внешних общих операций API:
Netty при передаче данных определяет, будет ли куча внешней памяти, если нет, то будет ли она упакована:
Все здесь мы берем **распределение объединенной памяти вне кучи в качестве примера для иллюстрации этой статьи. ** Выделение памяти кучи в пуле — это фактически тот же процесс.
Давайте посмотрим на демонстрационный пример распределения:
public static void main(String[] args) {
ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
//tiny规格内存分配 会变成大于等于16的整数倍的数:这里254 会规格化为256
ByteBuf byteBuf = alloc.directBuffer(254);
//读写bytebuf
byteBuf.writeInt(126);
System.out.println(byteBuf.readInt());
//很重要,内存释放
byteBuf.release();
}
Далее мы проанализируем его на основе этой простой демонстрации.
Класс входа в операцию
Инициализация PooledByteBufAllocator:
Вы можете увидеть операцию инициализации основного класса после его входа:
Теория распределения является Джемаллок, который можно понимать как реализацию Java-версии JEMALLOC.
PoolThreadCache
Из приведенного выше рисунка вы можете ясно понять основную структуру данных PoolThreadCache.
В начале в этих кешах нет никакой ценности.Только при вызове свободного освобождения (что будет объяснено в последующем освобождении памяти) ранее выделенный объем памяти будет поставлен в очередь кеша.Фактически, каждый распределение Когда пришло время проверить, есть ли что-то в кеше, если есть прямой возврат, если нет, то будет выполняться обычный процесс выделения (распределение памяти будет объяснено).
Давайте посмотрим на содержимое PoolArena directArena:
Давайте взглянем на структуру PoolArena.
PoolArena
Основную структуру данных PoolArena можно ясно понять на следующем рисунке.
В PoolArena структурам, соответствующим PoolChunkList и PoolSubpage, являются PoolChunk и PoolSubpage.Давайте подробно рассмотрим эти две части содержимого.
PoolChunk
В первый раз PoolChunkList, PoolSubpage являются значениями по умолчанию, вам нужно добавить Chunk, Chunk по умолчанию 16M. Внутренняя структура представляет собой полное двоичное дерево, которое будет состоять из 4096 узлов, 2048 конечных узлов (размер каждого листового узла страницы равен 8 КБ), размер памяти нелистового узла равен размеру памяти левого поддерева правого поддерева плюс объем памяти. размер .
Полная структура бинарного дерева выглядит следующим образом:
Это полное бинарное дерево представлено массивом в java.
Единственное предостережение заключается в том, что индексы начинаются с 1, а не с 0.
depthMap
Значение не меняется после инициализации,memoryMap
Значение изменяется при распределении узла.
Этого значения слишком много, чтобы делать скриншоты, то есть полное двоичное дерево выше представлено массивом, но сохраненное значение является не индексом узла, а глубиной сохраненного дерева.
Если значение массива depthMap равно 0, это означает, что может быть выделено 16 МБ пространства, если оно равно 1, это означает, что может быть выделено 8 МБ, если оно равно 2, это означает, что может быть выделено 4 МБ, если оно равно 3, это означает, что может быть выделено 2 МБ. выделено ……………… Если это 11, ему может быть выделено 8 КБ пространства.
Если узел был выделен, его можно установить равным 12.
Как определить размер потребности выделить в глубину?
Если выделяемой памяти меньше 8к после нормализации, то ее можно выделить на 8к (то есть глубина 11).
Если это 8k или больше, чем 8k, глубину можно найти с помощью следующего кода:
int d = maxOrder - (log2(normCapacity) - pageShifts);
Зная глубину, как найти этот узел? ? ?
Найдя узел, сначала отобразите занятый узел, а затем обновите родителя родительского узла родительского узла.Родительский узел выглядит следующим образом:
SubpagePool
На приведенном выше рисунке показана структура памяти SubpagePool. Когда мы выделяем страницу, мы знаем, выделена ли она в соответствии со значением memoryMap, так что, если это subpagePool?
SubpagePool делится на 2 категории: tinySubpagePools и smallSubpagePools, размер тоже для приведенного выше рисунка, каждая категория имеет фиксированный размер, если выделить размер 256b, то страница будет 8k, 8*1024/256 = 32 блока. Итак, как указать, что каждый из них также выделен?
private final long[] bitmap;
Поскольку тип long занимает 64 бита, нам нужно представить здесь только 32 бита, поэтому мы можем использовать тип long, 1 для каждого бита двоичного файла указывает на то, что он использовался, а 0 указывает на то, что он не использовался.
Поскольку подстранице нужно не только найти узел полного бинарного дерева, но также нужно знать номер и номер длинного, так что это сложнее:
Первые 32 бита типа long используются для представления первых битов подстраницы, а последние 32 бита используются для представления узла полного двоичного дерева, которое идеально.
Выделить ядра
Запись распределения: ByteBuf byteBuf = alloc.directBuffer(256);
Следуйте за кодом:
Посмотрим: PooledByteBuf buf = newByteBuf(maxCapacity);
Создайте объект PooledByteBuf. Наконец, возвращается объект PooledByteBuf.
Давайте посмотрим на структуру наследования классов:
All ByteBuf byteBuf = alloc.directBuffer(256); В этом предложении нет ничего плохого, и об ошибке не будет сообщено.
Давайте взглянем на подробную реализацию newByteBuf(maxCapacity):
Здесь используется технология пула объектов Recycler, реализованная Netty. Дизайн Recycler тоже очень деликатный.Вы можете написать статью о Recycler в будущем.Сегодня не в фокусе.Пока мы знаем, что из-за стоимости выделения объекта POLLEDBYTebuf нам нужно часто использовать объект PolledBytebuf, и если у вас есть какие-либо требования к производительности, то технология Pihua — хороший выбор.(Например, пул потоков, пул соединений с базой данных и т. д., которые мы использовали ранее, аналогичны.) Технология объединения** пулов в определенной степени снижает нагрузку на производительность, вызванную частым созданием объектов. ** На самом деле, эта подобная идея очень распространена (например, стоимость запросов к базе данных высока, кэширование в Redis, идея та же), и вы также можете испытать (PoolThreadCache) в продолжении этого статья.
Через PooledByteBuf buf = newByteBuf(maxCapacity); достаточно получить исходный объект.
Суть распределения: allocate(cache, buf, reqCapacity);
- попробуй сначалаРаспределяйте, находите разные кэши в соответствии с разными типами и возвращайтесь напрямую, если есть распределение.
- еслине может быть выделен, продолжайтеприсвоено выше.
После этого, в соответствии с выделенной страницей, выделение запрошенного размера (поскольку страница может хранить много величин одного размера) должно быть помечено длинным битом, указывающим, что позиция выделена, и родительским эквивалентным значением модифицируется полное бинарное дерево, и выделение заканчивается. Если чанка нет, то вам нужно выделить новый чанк и повторить вышеуказанные шаги.
свободное ядро
Запись выпуска: byteBuf.release();
Следуйте коду:
С помощью этого кода мы помещаем это в соответствующую очередь:
Кэш хранится в соответствующей очереди Cache.
Адрес исходного кода статьи на github:nettydemo, или официальный аккаунт отвечает на «Netty», чтобы получить адрес исходного кода.
Если вы чувствуете себя вознагражденным после прочтения, пожалуйста, поставьте лайк, подпишитесь и добавьте официальную учетную запись [Ingenuity Zero], чтобы узнать больше захватывающей истории! ! !