Nifty Memory Leak

So I have been wracking my brain trying to find out why Spoxel tends to start lagging about after an hour of gameplay. After a lucky break, a user noticed the spike in cpu usage that increased significantly over time in a section of code that adds and removes nifty elements. As it turns out, every element that nifty creates it calls:

nifty.getEventService().subscribeStrongly("style-refresh:" + getStyle(), styleListener);

When nifty removes elements… well it doesn’t remove them from this event service. Also, every time you add an element it calls this method which iterates through every element in the list that matches the key. As far as I can tell that subscription isn’t used under normal operation (I removed it in my local copy of nifty and have had 0 issues so far). I’m not sure why they set this up since it is far cheaper to just walk the nifty scene graph and check for styles to update. Instead you get to pay a performance penalty any time an object is added or removed as well as never freeing any of the memory since it’s never removed from those internal lists.

If I wasn’t so close to release I probably would switch away from nifty at this point.

3 Likes

Please open an issue:

Issues · nifty-gui/nifty-gui · GitHub

1 Like

I meant to do that earlier but it looks like I forgot to hit submit. It has been added now. I’m not holding my breath since Nifty is basically just in maintenance mode now.

2 Likes

That is true but they seem to be accepting PRs still, quite fast actually.

1 Like

Sorry to necro this but in case anyone is still interested…

Okay, the issue is this Memory Leak in style event service · Issue #461 · nifty-gui/nifty-gui · GitHub. And there is a PR that’ll fix it, but in case there wont be integration for this nor another release… I’ve created a fork that I use myself and has the fix included. I say this because you can use it too.

Although… that being said… I will not support or maintain Nifty anymore than is required for my own goals. You can either fork my fork, or use it through Jitpack.

Through Jitpack (Gradle):

  • Make sure you have the source listed maven { url “https://jitpack.io” }

  • Override jME’s Nifty like so

configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module(‘com.github.nifty-gui:nifty’) with module(‘com.github.tonihele.nifty-gui:nifty:1.4-SNAPSHOT’)
substitute module(‘com.github.nifty-gui:nifty-default-controls’) with module(‘com.github.tonihele.nifty-gui:nifty-default-controls:1.4-SNAPSHOT’)
substitute module(‘com.github.nifty-gui:nifty-style-black’) with module(‘com.github.tonihele.nifty-gui:nifty-style-black:1.4-SNAPSHOT’)
}
}

The fork:

3 Likes

Just for documentation: I had the same problem in my own game a few years ago - There, you could close+restart a jME app over and over again and I noticed the memory was not cleaned up after closing. I just removed the subscribers in cleanup (when the app was closed) in that case:

@Override
public void cleanup() {
    super.cleanup();
    // Unsubscribe leftover element styleListeners to prevent memory leak (https://github.com/nifty-gui/nifty-gui/issues/461)
    for (Nifty nifty : runningNifties) {
        nifty.getEventService().clearAllSubscribers();
    }
}

… which solved the memory issue. I don’t know the Nifty internals too well, but I assume if you care about memory leaking while the application is running, you have to take a different approach, which you probably did in your fork. But if it’s only a matter of purging all subscribers once when the app closes (to clear all references and enable the GC to work), you can take the official Nifty dependency and use the above workaround.

2 Likes

Thanks, that is also what I initially did. But that doesn’t fix the memory leak that is happening in-game, given that in game means that you are using one screen and always modifying it. Also it still has the performance problem with the subscriber system. This is a problem if one does a lot of modifications at run time. But totally viable otherwise, more hassle free than using the fork.

And of course if you wipe out the subscribers on wrong occasion you will also wipe out the ones you need (the regular event stuff, such as reacting to component changes/requests).

1 Like