Bug Closed · Resolved

ModelUpdaters.uuidLookup and ModelUpdaters.idLookup are strong-reference maps, and ModelUpdaters.tick() ▎ performs no liveness checking. Entries are only removed by explicit remo

#10054 opened by juin 3 weeks ago

juinreported this bug

ModelUpdaters.uuidLookup and ModelUpdaters.idLookup are strong-reference maps, and ModelUpdaters.tick() performs no liveness checking. Entries are only removed by explicit removeModeledEntity / forceRemoveModeledEntity.

EntityListener registers onDeath(EntityDeathEvent) but never calls removeModeledEntity. The only automatic cleanup path is WorldListener.onEntityUnload (EntitiesUnloadEvent).

As a result, a modeled entity that dies while its chunk stays loaded is never released. On a farm area that is permanently loaded, this accumulates without bound.

Evidence — heap histogram after ~9h uptime, ~28 players

ModeledEntityImpl                              431,354
ActiveModelImpl                                431,354
net.minecraft.world.entity.monster.Zombie      454,650   (585 MB)

The retained zombies drag their full server-side state — per leaked entity roughly:

  • 15.5 WrappedGoal
  • 9.8 AttributeInstance
  • 68 pathfinder.Node
  • 20.1 SynchedEntityData$DataItem
  • 47 DataTracker
  • 39 StateNode lambdas

Totals in the same histogram:

pathfinder.Node     30,909,919
Object[]            47,854,531   (6.67 GB)
DataTracker         21,427,812

Impact

Heap grows ~4 GB/hour independent of player count (flat at 25–30 players, ~800–1,200 world entities throughout).

A 38 GB heap is exhausted in 8–9 hours, after which the server enters a Full GC loop:

  • 899 Full GCs in one session
  • 5,350 ms average pause
  • 8,759 ms max pause

The watchdog then kills the server.

The server must be restarted roughly every 8 hours.

Setup that triggers it

MythicMobs mobs applying:

model{mid=...} @self ~onSpawn

spawned and killed continuously (~4–5 kills/second) in a permanently-loaded area.

Suggested fix

Release the registry entry when the entity is removed from the world, not only when its chunk unloads — e.g. hook EntityRemoveFromWorldEvent (Paper) or call removeModeledEntity from the existing EntityListener.onDeath handler.

Happy to run further queries against a heap dump or test a patched build.

Sign in to join the discussion.