Heap dump of the JVM

Hi Johan,

it is hard to see the issue without seeing the code. We would do the same as we suggest: use heap dump on the Java side and Instruments on the native side. One thing we would try to confirm is that the retain count of the stuck views is actually 1.

Nat/J’s logic is that when the retain count drops to 1, then it will replace its strong native Objc to Java reference with a WeakReference, because that last retain is actually the one held by the Java side, so when the finalizer frees that object, it will be released.

If you can confirm this, then the next place to check is whether the finalize() method of your ViewController Java object is actually called. If you follow up the inheritance chain, you will see that the finalize() in NativeObject will call the peer.release(), which will do the actual release of the native object. You should be able to follow it there to see if the release actually happens. (e.g. checking the retain count of the peer’s address in Xcode at the same time).

Best Regards,
Gergely

I have created a basic project that does some of the things my troublesome main project does: load images, scale them and put them in UIImageViews. I figured that would introduce the problem, but the memory usage stabilizes at some point. I am going to look further, and see if I can find a reproduction scenario.

No, I used the marking of generations of allocations profiling. This showed me an increase of about 3 MB per screen.

In that case I highly recommend the memory graph feature of Xcode 8, it might help you to find what causes the leak you experience.

It looks very impressive, but there are so many memory tags and allocs, that I do not have a clue where the problem is.

Here are the backtraces of the first screen (a tree of views, image views and labels, without event listeners) that we show. I exercised the application a lot, during which there were many explicit garbage collection requests. But the first screen is still in memory after about a minute. I have no idea how the notification center got involved for this screen:

And here are the references to and from the same object on the JVM heap:

Haven’t you registered the view for observation?

I do not observe anything of this screen directly. I do have global observers (keyboard shown/hidden, text field/view begin/end editing) that I register in applicationDidBecomeActive(...) and unregister in applicationWillResignActive(...) of my application delegate. The first screen does not have text fields or text views.

When I disable all global observers, the backtrace to the notification center is gone, but the iOS object for the screen still exists.

Note that at this point I have disabled almost all UI elements of the screens that I test with, but I still see that iOS and JVM objects are not being freed. Even though the JVM heap dump shows that there are no more incoming references from my own objects. I would expect that JVM peers would get garbage collected, and that that will decrease the reference count. If the iOS object is not disposed of at this point, and rertrieved again by Java code later, I expect a new JVM peer to be created because the previous one was garbage collected.

Considering you use notification server for your application delegate, the node to which the notification points to might be your application delegate which in turn transitively holds a reference to the stuck object.

Are you sure, your application delegate does not have any field that may capture (directly or indirectly) the view?

Definitely. I may have a variable somewhere that may hold on to a single screen/view, but I definitely do not have collections of screens/views in my own objects. See the instance of SplashScreen in my previous screenshot. It does not have incoming references from any of my classes.

If you want, I can send you a JVM heap dimp so you can inspect it yourself.

Okay, please send it over, then.

I am still looking into the hprof file you have sent over.

Could you please set an exceptional breakpoint to dalvik.system.CloseGuard and tell me at what positions it is getting thrown?

I set a breakpoint on the following line of warnIfOpen(), but it is never reached:

String message =
        ("A resource was acquired at attached stack trace but never released. "
         + "See java.io.Closeable for information on avoiding resource leaks.");

If I set a breakpoint at the start if warnIfOpen(), the debugger halts at this line. I can see that ENABLED is true, so I correctly enabled CloseGuard.

I also enabled all (Java) exception breakpoints. These get triggered a lot during start-up. If I disable them until after I visited each screen at least once, they are nog longer triggered. Not even when the application crashes eventually.

Are those Views implemented entirely in Java or mixed with ObjC?

There is only Kotlin code for those views. Here is an example:

class CommandBar private constructor(peer: Pointer) : UIStackView(peer) {
    @Selector("init")
    external override fun init(): CommandBar

    fun initialize(commandWidgetFactory: IosCommandWidgetFactory, vararg commands: Command) {
        setTranslatesAutoresizingMaskIntoConstraints(false)
        setAxis(UILayoutConstraintAxis.Horizontal)
        setDistribution(UIStackViewDistribution.FillEqually)
        setAlignment(UIStackViewAlignment.Fill)

        commands.forEach { command ->
            addArrangedSubview(commandWidgetFactory.create(command))
        }
    }

    companion object {
        @Selector("alloc")
        @JvmStatic external fun alloc(): CommandBar
    }
}

Could you please add a method breakpoint for

org.moe.natj.general.NatJ.removeStrongReference

right before the view should be released and then check if it gets invoked or not? If not, then that will probably mean that there is a reference somewhere from the native side.

We have created a reproduction project. It is available here: https://github.com/squins/moe-non-freed-objects-issue

Details are in README.md on github.

Can you have a look at it? Thanks in advance!

Hi Kees,

thanks for the test project, we will take a look on Monday.

Best Regards,
Gergely

1 Like

Hi Gergely,

Have had any chance to look at our sample project?

Thanks!

Best Regards,

Kees

Hello

I am currently looking into it.

Daniel

There were some issues in your code.

  • Because alloc methods return with strong ownership, bindings must be marked with @Owned annotations.
  • System.gc() in itself is too lazy, use Runtime.getRuntime().gc() instead.

I have created a pull request containing these modifications.

Hope this helps.

1 Like

Thanks a lot for your advice. The combination of adding the @Owned annotations, asking the runtime to collect garbage when switching screens, and the dismantling of the view hierarchy, have resolved a lot of the retained memory. I still see some memory being retained when I use specific screens, but I will look into that.