Heap dump of the JVM

I tried that and initially I saw quite a bit of leaks. But then I added a call to System.gc() and I saw the amount of leaks drop significantly. Now I see about 60 leak locations. The location that has leaked the most has leaked 2 kiB, so in total not even 120 kiB is reported as having been leaked. Taking into account that some of these leaks are objects held by Java objects, I think the leakage is minimal.

No, this is a very basic application with at most dozens of views on a single screen.

Here are some more details about what I am seeing. I switch between 3 different screens. Every time I switch to a new screen (i.e. the root view of the window of my application delegate is changed), I see an increase of about 3 MB in real mem. Once this reaches 100 MB, it no longer increased, and even goes down to 94 MB sometimes. When I continue switching screens, the app crashes at some point. So the screens work fine many times, but suddenly there is a problem.

I see the same memory increase in the debugger graph. When the app crashes, the debugger stops with an EXC_BAD_ACCESS fault. I assume this fault is due to our code not checking for null properly and passing a null received from an API call to the next API call.

Where did you insert System.gc ?
And if you have a native NPE, the best you can do is to find related java-stack.

Just after changing the root view of the window of my application delegate.

That is the strange thing. I do get a stack trace in Xcode sometimes complaining that I pass in a null. But it never shows up in my own exception log.

The EXC_BAD_ACCESS fault does happen in Core Graphics code, so I simply assumed that must be because I am passing that null somewhere.

To dump the heap, you can use the following snippet:

List<NSURL> l = (List<NSURL>) NSFileManager.defaultManager().
        URLsForDirectoryInDomains(
                           NSSearchPathDirectory.DocumentDirectory, 
                           NSSearchPathDomainMask.UserDomainMask);
        NSURL docDirURL = l.get(0); // Error handling is for cowards :) 
        String fsPath = docDirURL.fileSystemRepresentation();
        try {
            VMDebug.dumpHprofData(fsPath + "/MOE-Dump.hprof");
        } catch (IOException e) {
            e.printStackTrace();
        }
2 Likes

To help with your specific case, we would need to see the code (or a test case that shows the same symptoms).

In general, Nat/J bindings take care of memory management. There are a few corner cases, where memory management is not intuitive for the Java developer. Some of these are documented in the Nat/J docs.

There are some cases, where a relatively small number of Java binding objects cause the system to allocate large chunks of memory on the native side. The problem with this is, that the Java heap itself will not fill up, but the native heap usage can rise quickly to the point where iOS kills the app or it crashes.

This is not that frequent, it only happened to us, when we were allocating large texture buffers very quickly (high resolution video playback on an OpenGL texture). In such cases, while there is no direct memory leak, new chunks are allocated faster than the finalizer thread can deal with the old ones. This is why the ObjcRuntime.disposeObject API was born. It essentially frees the native part of a binding object: the Java object will be dealt with by the GC.

We also saw some cases, where the Java objects (e.g. ViewController) were somehow kept around, and thus the larger native allocations were also kept around.

As I wrote in the beginning: it is hard to help without seeing the code.

I have time to work on a reproduction project in the next couple of days. I will let you know when it is done.

I have been letting Nat/J handle memory management since you clarified things in: Working with iOS Core types

I never call an unsafe method in my code. But I do need to explicitly manage objects returned by Core Graphics functions that contain Create, Copy or Alloc in the name, right? For example:

val bitmap = CGBitmapContextCreate(...)
try {
    ..
} finally {
    CGContextRelease(bitmap)
}

Or are these methods using the alloc().init() allocation method underneath and will Nat/J handle it?

1 Like

Dear Johan

Only ObjC support has automatic memory management, but Core Foundation is a C framework, thus you do have to do manual memory management on your own.

How can I get the file later ?

You should be able to copy it off the device using Xcode.

With the memory dump I was able to determine that my screens and associated views are not being garbage collected. I figured there must be a cycle in the reference counts on the iOS side, so I now dismantle the whole view tree in viewDidDisappear(Boolean) of my UIViewController. Unfortunately this does not seem to have an effect.

For example, I still see many instances of UIImageView. Many with no incoming references, or a combination of WeakReference and FinalizerReference references. The only outgoing references for this class are the peer pointer and some Kotlin-generated members.

What is preventing these views from being garbage collected?

Have you tried the Instruments app? It comes with Xcode.

https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/FindingLeakedMemory.html

1 Like

Yes, I did. And using the generations feature I see that my memory is increasing.

The issues I have now is that I don’t understand why some JVM objects are not removed from memory (even though I perform a lot of forced garbage collections, which actually do remove a lot of objects). I am afraid that the JVM objects is what is keeping the iOS objects from being removed from memory.

If the amount of memory of the iOS objects is much bigger than the amount of memory of the JVM proxies, then it is possible that you run out of iOS memory before you run out of JVM memory. For example: Bitmaps can take dozens to hundreds of kilobytes of iOS memory, but their JVM counterparts may take up only a few 100 bytes. So a factor of 100-1000 times more iOS memory for a set of JVM objects is not unusual.

Suppose I have 100 kB of JVM objects, then I can have 100 MB of iOS objects. The JVM won’t remove the objects from memory because the JVM has plenty of heap space available. The app crashes/is killed because it consumes too much memory.

Do you mean the memory graph feature of Xcode 8 by debug graph?

https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/debugging_with_xcode/chapters/special_debugging_workflows.html#//apple_ref/doc/uid/TP40015022-CH9-DontLinkElementID_1

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?