I've found what appears to be a very severe memory leak in the DataChartView component. Something related to bitmap caching or something possibly related to synchronized scrolling. Sample project here:
https://dl.dropboxusercontent.com/u/56947838/MemoryLeak.zip
It manifests when you rotate the device, and with this sample it takes 4-5 rotations before the process runs out of memory.
This is with the public CTP builds, but other builds we're using also exhibit similar problems.
I've replicated on both a real device (Samsung 4.4) and a Genymotion Android 5.x emulator.
Thank you!
Hi Ben,
It's possible this is not the scenario that we've seen before and it could be an interesting leak related to the sync settings. We'll look into that and see if it is a new scenario. A good way to tell is this. If you rotate the device pretty slowly (maybe try waiting 10 seconds between rotations?) does the issue still occur? If yes, this scenario probably represents some form of actual leak related to the synced charts stuff. However, if slowing the creation/destruction down avoids the scenario, then we have a different non-leak scenario.
If this is the same issue I've seen before, it isn't actually a leak, per-se, but rather an artifact of some of the more strange ways that Anrdoid deals with bitmap memory. The long and the short of it is, that when your app runs out of heap memory, the Dalvik VM forces a garbage collection to occur. Critically, though, Android bitmap classes do not release their native memory until they are processed by the finalizer queue, not when they are first collected.
This means that it is a common problem on Android that if an application uses a lot of images and you destroy and create views that contain them, you can wind up with enough partially collected images that an OutOfMemoryException can be thrown, even though none of the offending bitmaps are actually still referenced.
You can proactively destroy bitmaps with the recycle() method to avoid this.
But how does that relate to the chart? We need to use multiple layered bitmaps to render the content of the chart. If you have a device with a humongous resolution and the chart is taking up a lot of space, these bitmaps are BIG. However, the standard maximum heap allocation for an Android app is, by default, very small, so you can only have a few sets of these images in memory at a time unless you:
Note, with more recent builds (non-public) we have mitigated this issue by removing an unnecessary background layer from the component, meaning each chart takes less memory, but you may still be able to reproduce the issue if you cause views containing the chart to be created and destroyed quickly enough. This way there are more released bitmaps that haven't been finalized than the system can cope with.
The short version is that if you do either of the above bullets, we think you should be ok, since that is our experience, but definitely let us know if you can still reproduce the issue. Last time we looked into it, it definitely didn't seem like a leak. If it were leaking as bad as bad as your scenario would imply, we wouldn't be able to scroll through every chart sample in our Samples Browser without it crashing, as we do. Rather, if you have a small heap (as you do by default in Android) and are creating new views without proactively destroying the images contained in the old views you can cross a threshold where you have too many released but undestroyed images, and cross the heap limit threshold.
BTW, the reason you are able to hit this via rotating the device is that the default behavior in and android application is to destroy the layout and recreate on an orientation change. You can modify this behavior and let your app resize its views instead and that would also be a valid way to avoid this scenario.
Let me know if you have any questions about this.
-Graham
Also, note, we'll be looking whether there are other ways to make the chart more proactive about releasing its bitmap content. However, all of the avenues we thusfar tested were unsatisfactory compared to providing a method to let us be proactively notified when you were done with the chart.
Thank you for the reply.
I added calls to your DataChartView.destroy() at appropriate times (Fragment.onDestroyView() in our code) and it doesn't improve matters.
The leak is easy to spot in an instance of SyncLink. It grows every time a sync'd pair DataChartView is created. This is not an Android VM bitmap caching issue. I'm very well versed in those types of issues and I'm sympathetic, but this ain't that. Modern VM's are very good at Bitmap reclamation anyways, so that argument is not one easily made these days.
I've attached a screenshot showing a MAT trace of a heap dump that shows 35mb of retained heap after 7-8 DataChartViews are destroyed/recreated (with calls to .destroy as well).
https://dl.dropboxusercontent.com/u/56947838/Screen%20Shot%202015-03-30%20at%2012.42.53%20PM.png
The mitigation strategies you propose are, unfortunately, all non-starters for our app for a variety of reasons. They all would just cover up the leak in DataChartView however.
Thank you.
Graham/Andrew - Thank you for your work on this issue. I am working with Ben on this project. We are cloning an iOS application that heavily uses NucliOS. The stability of the Android charting components is extremely important to us and our clients. We trust this is a high priority for your team and a fix can be found soon.
Caylan,
Just as a reminder, here, by the way. Infragistics Android is a pre-release product, specifically because we cannot yet assert RTM level stability. We, of course, hope that you don't run into any more issues, but there is a reason we aren't calling this RTM yet.
Hope this helps!
It reminds me of that funny quote: What came first, the bugs or the users? :)
Good luck, and let us know how we can continue to help.
The potential leak that I'm seeing should be evaded if you call
setSyncChannel(null);
In addition to destroy() on the charts when the orientation change happens.
I gave this piece of advice to Andrew and he hasn't seen the exception after quite a few rotations so far. So hopefully that is a viable work around for you guys.
He's calling destroy on both charts and setting the sync channels to null during the Activity's onDestroy method. Let me know if that helps.
The root issue here is that the destroy() method should be doing a bit more than it does (namely cleaning up some sync channel stuff). Hopefully that work around can tide you over till we fix it in the product.
There's a potentially deep recursive call that gets executed as part of the line simplification routines. Under normal circumstances this should get nowhere near the stack size limit, but certain line shape create a degenerate case, I believe. We only hit the stack limit once with one data shape with this logic on desktop in over 5 years, and fixed that case a month or 2 ago.
However, Android likely has a much lower stack size upper limit than desktop given how it partitions memory between processes. I know the fix we made for this isn't in the latest public release for Android yet, but am I correct in that we give you an untested build?
If that is the case, and the issue isn't resolved, then either the fix we made for desktop hasn't been propagated to the Android release, or we need to adjust some parameters of the fix to deal with the smaller stack limit for Android. Let me check the status of the fix first, but if you could let me know whether you are running a recent private build, that would be appreciated.
Gotcha. We worked around this by disabling syncing (a nice to have feature).
The other problem with fastFlatten, though (different forum post)... that's our pain point today. We don't have a workaround for it. And for that reason it is impeding our business. If you have an easy fix for that we would be very grateful.
Note, though, that Andrew has been running a sample he created that isolated the problem with the sync setting, he'll try it out with the exact sample that you sent soon.
Great, we'll test and get back to you.