Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible memory leak: instances not released from memory after closing #57

Closed
xcporter opened this issue Jun 17, 2020 · 6 comments
Closed

Comments

@xcporter
Copy link

Java version : 13.0.1
JavaFX version : 11.0.2+1
JPro version : 2019.2.2

I'm building a tornadoFX/javaFX app, which is served via jpro. I've been chasing this memory leak for a few weeks now, and after closing a few thread leaks that were my fault, I'm fairly certain it has to do with the way the app is served. I'm noticing that the largest objects tend to be NIO HeapByteBuffers referenced to javafx eventually.

Here's the code I'm using to launch the app and set up scope:

class WebScope(val stage: Stage) : Scope() {
    val webAPI: WebAPI get() = WebAPI.getWebAPI(stage)
}

class Main : JProApplication() {
    val app = EfasApp()

    override fun start(primaryStage: Stage) {
        app.scope = WebScope(primaryStage)
        app.start(primaryStage)
    }

    override fun stop() {
        app.stop()
        super.stop()
    }
}

Is there something more I should be doing to release these instances from memory when the window closes? Or perhaps I'm missing something in the jvm args?

jpro {
    JVMArgs << "-Xms350m"
    JVMArgs << "-Xmx350m"
    JVMArgs << "-XX:+UseG1GC"
    JVMArgs << "-XX:MaxGCPauseMillis=200"
    port = 8080
}

Here's a heap profile, letting it idle for a few cycles, starting a new window, waiting then logging in. Max activity was two concurrent users. After closing all windows and opening one more, the amount the GC clears was reduced by about 20mB, the same occurs every time a new instance is opened.

Screen Shot 2020-06-16 at 9 27 45 PM

@FlorianKirmaier
Copy link
Contributor

This is most likely a memory leak in TornadoFX.
Can you tell me which version of TornadoFX you are using?
Is there a HelloWorld which is most similar to the setup of your test?

@xcporter
Copy link
Author

I'm using TornadoFx version: 1.7.20

I agree it might have something to do with how I'm switching between views, or possibly something to do with the TornadoFX EventBus.

Here's a simplified version of what i'm working on, with the same menu structure and a few drag and drop examples:

https://github.com/xcporter/memtest

It's just graphics, so hopefully that can rule out anything coming from the http client.

In this smaller version, things started acting weird once I had two instances open at once. Then, when I closed one of the instances, several "scala-execution-context-global" threads opened. When I closed the last instance, some threads finally closed, except for one of the scala threads from the first closing.

@FlorianKirmaier
Copy link
Contributor

FlorianKirmaier commented Jun 19, 2020

1.)
The memory leak is probably quite fixable with some knowledge about the inner workings of Tornadofx.
Every instance gets added to primeryStages but never gets removed.
Somehow the deregister methods must be called: https://github.com/edvin/tornadofx/blob/82e39f2f7e7b2f81a901347c47c674f44aa0b2c6/src/main/java/tornadofx/FX.kt

2.)
There seems to happen a lot related to Singletons in either TornadoFX or in your application. You have to make sure that the objects are not singletons but somehow bound to the scope of the instance.

3.)
We've made a library to write unit-tests for memory leaks: https://github.com/Sandec/JMemoryBuddy
That might be helpful to assure that your application and/or that TornadoFX works properly

Edit: I'm sure the issue is not related to JPro or the threads created by scala and the web framework

@xcporter
Copy link
Author

Thank you for the marvelous tools! An objective memory test is exactly what I've been looking for...

I'm closing since it's definitely an issue with the UI that doesn't show up unless it's cohabiting a jvm with other instances. That and the EventBus is definitely misconfigured.

I've set up JMemoryBuddy and TestFX, following the setup from SpaceFX loosely, and translating to Kotlin on the way. I have yet to get a result on any tests other than "uncollectable"--am I using this correctly? The SpaceFX example had a function called workaround that added and removed a blank stage, so perhaps I'll try that route.

internal class MemoryCheck : ApplicationTest() {
    lateinit var primaryStage: Stage

    @Test
    fun `Container View is Collectible` () {
        primaryStage = FxToolkit.registerPrimaryStage()
        val view = ContainerView()

        JMemoryBuddy.memoryTest {

            interact {
                primaryStage.scene = Scene(view.root, 325.0, 575.0)
                primaryStage.show()
                primaryStage.toFront()
                primaryStage.close()
            }

            FxToolkit.cleanupStages()

            it.assertCollectable(view)
        }
    }
}

@xcporter
Copy link
Author

related: edvin/tornadofx#1251

@FlorianKirmaier
Copy link
Contributor

To comment on your JMemoryBuddy-test.
The problem with the TestApplication in TestFX is, that they are referencing various objects in the class itself.
A variable you want to check with JMemoryBuddy should be inside of the lambda, otherwise, it usually is already referenced which breaks the test.
For example, the view and the stage should be inside the lambda of JMemoryBuddy. After that, you have to make sure, that the stage is also not referenced in the TestClass.
I would recommend starting with a simple test which must be true and slowly extend on it.

The workaround function in SpaceFX is a workaround a bug in JavaFX: openjdk/jfx#153

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants