Я использую большой сервер Minecraft (Spigot) с проприетарным, сильно запутанным плагином AntiCheat, а также с нашим собственным набором плагинов, проверенных в бою. Сервер много раз создает и выгружает новые миры, поэтому после выгрузки остаются CraftPlayer
и CraftWorld
объекты для GC.
Мы работаем на Java 13 (то же самое происходит на Java 12) с G1GC и так называемыми"aikar gc flags"
java -XX:+UseLargePagesInMetaspace -XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions -XX:MaxGCPauseMillis=100
-XX:TargetSurvivorRatio=90 -XX:G1NewSizePercent=50
-XX:G1MaxNewSizePercent=80 -XX:G1MixedGCLiveThresholdPercent=35
-XX:+ParallelRefProcEnabled -jar Spigot.jar
AntiCheat хранит слабую ссылку на CraftPlayer
с, и все же они имеют утечку, и при достаточно большой утечке ЦП сервера очень сильно вращается, чтобы в конечном итоге умереть с OutOfMemoryError
. Запущенный вручную System.gc()
также не очищает их.
Когда AntiCheat отключен, утечка исчезла.
ЕДИНСТВЕННАЯ ссылка gc-root в heapdump на все утечки CraftWorlds
и CraftPlayer
- это запись в WeakHashMap
, ключ CraftPlayer
. (CraftPlayer
и CraftWorld
перекрестно ссылаются друг на друга, прежде чем стать обычным GCd).
Существует способ сделать утечку с помощью WeakHashMap
: устаревшие «просроченные» записи не будут удалены, есливы не активно используете get () / set (), которая вызывает expungeStaleEntries()
. Возможно, это было бы причиной, если бы AntiCheat использовал что-то вроде Map<String, WeakHashMap<...>>
, но это не тот случай, он использует один основной WeakHashMap
.
И здесь есть одна загвоздка: при такой утечке вы можете видеть, что очередь WeakHashMap
будет не пустой! А пока пусто, как я проверял! Я также заметил, что WeakHashMap
обернут synchronizedMap
, но мне не кажется, что это может быть проблемой.
Это действительно ошибка GC, вызванная одним из флагов?
Этот шаблон повторяется каждый раз и в каждой куче: