Комментарии 1
Хороший пример.
Но в вашем случае явно виден источник утечки в меткиках DirectBuffer Pool (количество аллокаций и суммарный размер). И включив NMT вы увидите прогрессирующую статистику. Дальше можно снять хипдамп и увидеть ненормальное количество инстансов класса.
Но у DirectBuffer есть очень нехороший паттерн поведения, когда RSS процесса растет, а все метрики по Native Memory в норме, что может привести к системному OOMKiller.
Это связано с тем, что под капотом DirectBuffer pool никакого пула на самом деле нет. Есть только Atomic каунтеры количества и размера. А механизм это тривиальные malloc() - free(), ну и плюс к объекту, использующему этот "пул" прикручивается java.lang.ref.Cleaner (c 11 версии). Поэтому и зачистка только во время GC. В итоге получается, что в нативной памяти процесса создаются и удаляются "кусочки памяти" которые ведут к её фрагментации и распуханию RSS, который невозможно диагностировать стандартными метриками. Обычно утечка проявляется на хорошо нагруженных сервисах обрабатывающих http/grpc.
Раньше с проблемой бороться было невозможно, кроме перезапуска приложения, но в glibc с версии 2.8 завезли malloc_trim() который умеет во всем адресном пространстве процесса релизить "залипшую память".
В JVM в DiagnosticCommand сделали метод trimNativeMemory() который можно дернуть через JMX MBean (в контейнере) или через jcmd в обычной инсталляции. А в JVM 17.0.9 сделали ключ -XX:TrimNativeInterval (пока экспериментальный) который взводит таймер регулярных malloc_trim()
С точки зрения GC, проблему, описанную в статье на небольших сервисах "лечил" паллиативно путем использования ZGC (утечки были в зависимостях, не в нашем коде). У G1 да, были проблемы с утечками буферов, но вроде бы они её вылечили и они чистятся сейчас в mixed фазе, но это не точно.
Опыт отладки хитрой утечки прямой памяти