ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51.09k stars 13.51k forks source link

Memory leak in state change #1096

Closed perrygovier closed 8 years ago

perrygovier commented 10 years ago

It appears that changing state increases memory usage with every transition. Here's a simple example. Open your browser's memory profiler and click "Get Clicky".

http://codepen.io/perrygovier/pen/FJgBa

Pages with more stuff on it tend to increase the amount of memory that gets eaten with each transition. Note the number of nodes and listeners never decreases.

screen shot 2014-04-09 at 5 31 45 pm

On my app that uses a few larger lists, the app can quickly get to 200mb after just a few mins of manual clicking back and forth. I have yet to crash the app due to memory pressure, but it seems more a matter of impatience than something that wouldn't happen with continued use.

I believe this is less an issue with Ionic and more to do with Angular's UI Router, but I thought I'd start here as it's where it popped up for me.

BountySource discussion that appears related

perrygovier commented 9 years ago

Great to hear, thanks!

rajatrocks commented 9 years ago

@mica16 - can you please describe a bit more about how you're effectively cleaning up your modals? I use them extensively in my app, and am also having memory issues. Thanks!

mica16 commented 9 years ago

Here's how I do:

$scope.openModal = function () {
       $ionicModal.fromTemplateUrl('/myModalTemplate.tpl.html', {
                scope: $scope,
                animation: 'slide-in-up'
            }).then(function (modal) {
                $scope.modal = modal;
                $scope.modal.show();     
            });
        };

$scope.closeModal = function () {
       $scope.modal.remove().then(function () {   //it's the most important part, do not do that in scope's destroy method if you use cached views since it might never be called !
             $scope.modal = null;
       });
};

In my template so, I call openModal() and closeModal() wherever I want.

Hope that helps you @rajatrocks

rajatrocks commented 9 years ago

That's great, thanks @mica16 !

mica16 commented 9 years ago

Does it solve your memory leak issue?

rajatrocks commented 9 years ago

I will try it tomorrow (it's 3AM here right now and I'm afraid to touch any more code :) and let you know!

mica16 commented 9 years ago

Ok no problem !

rajatrocks commented 9 years ago

Hi @mica16 - how are you measuring memory leakage? I've tried both recycling modals as well as creating and destroying them on each use, and looking at the memory usage in Xcode I see it going up in both cases. Thanks!

mica16 commented 9 years ago

With Batarang (tools on Chrome), I noticed that the performance for watches increase and increase without doing anything but opening a modal and closing it. It would be normal through XCode that the memory increase a few. The point is that it should stabilize, often decreasing.

For instance in my case, my app started at 50mb, then with the issue of modal, increases to 100-130 mb and even more !... After refactoring my code as shown, my app never exceeds 80mb.

rajatrocks commented 9 years ago

Thanks @mica16. The iOS version of my app has become very unstable since upgrading to beta 14 and crashes frequently with out of memory errors now. Android seems fine. I will have to dig deeper.

thuey commented 9 years ago

I'm in a similar situation as @rajatrocks. The iOS version of my app keeps increasing in memory usage with each tab change (even going back and forth), and eventually crashes with a memory error. It's exactly like the video shown here: http://forum.ionicframework.com/t/memory-leaks/10036. I used Chrome's profiler to check memory usage in the browser and on Android. Both seem to be fine. I'm using the latest version of ionic (v1.0.0). I really appreciate the work you guys are doing, but can you please re-visit this issue?

tslater commented 9 years ago

I'm having the exact same issue on the latest version of ionic... Looks like this is nothing new. http://forum.ionicframework.com/t/memory-leaks/10036 Ionic is basically unusable for us with these kinda leaks.

thuey commented 9 years ago

After further testing, iOS7 is okay. Switching back and forth between tabs, memory usage increases with each new tab, but after that remains fairly constant. On iOS8, however, memory usage continues to build up until it crashes.

iOS7: screen shot 2015-06-03 at 4 56 40 pm

iOS8: screen shot 2015-06-03 at 4 55 10 pm

ffabreti commented 9 years ago

@mica16 I cannot thank you enough... Ionic docs are indeed wrong about modal remove on view destroy !

surfjedi commented 9 years ago

We are having this same issue. On the lastest ionic 1.7.7, and using iOS devices, on iOS 8.4, 9.1, 9.0, etc.

Drastically affects navigation, and after a while can lock app ui

EduardoNE commented 9 years ago

We are having this same issue here.

kdudek89 commented 8 years ago

We are having this same issue here.

MayankLogiciel commented 8 years ago

Facing same problem. Whenever i to navigate to a new state, 10-15MB gets added to used Ram by App and easily it cross 200MB mark. I updated my ionic lib to v1.1.1 but it still the same. Also my app very frequently crashing on some android devices.

sdhull commented 8 years ago

I'm facing this problem as well. Like many others, I'm not terribly experienced hunting these issues down, however after playing with Timelines in chrome dev tools, it's pretty obvious this is a problem with my app.

I've upgraded to the latest version of ionic (1.2.4) so perhaps there's something I'm doing that's causing issues? But the thing that seems suspicious to me is that nodes & listeners only grow, especially during state transitions. It seems like sometimes nodes (& listeners) should eventually be removed, right?

screen shot 2016-01-13 at 3 45 55 pm

The primary thing I was doing was switching between pages in my app (Account & Add New) -- that's what was causing the stair-step up pattern of nodes & listeners. I'm not using the Tab controller, but rather using $state.go.

This is much, much worse in Android <4.4 -- tho of course I can't use chrome dev tools to inspect my app on those versions of Android. But judging by app memory usage on the Samsung test device I have, it gets out of control much quicker.

sdhull commented 8 years ago

If you want an example to play with, try out my repo I created to demo a bug with current version of ionic & fullscreen (it's a barely-modified tabs example app). You'll probably have to bower install and then do ionic run android. Make sure it's a recent enough version of android to support inspecting. Start recording a timeline in chrome dev tools and click around, you'll generate a graph that looks like this:

screen shot 2016-01-13 at 4 59 12 pm

In the beginning I was switching between tabs. In the middle, I was just clicking into a chat and back. At the end I went back to switching between tabs.

This is a project I spun up yesterday evening, with all the latest everything. I'm running API 21. I'll try with a newer API as well.

sdhull commented 8 years ago

FWIW, I was unable to repro in just Chrome and I don't really know how to profile in iOS. I tried the Profiler tool but it measures at the Objective-C level, so not sure what I'm even looking at. Poking at my app produced a few object leaks but they measured like 50-150 Bytes. Total "Persistent Bytes" seemed to stay in control ranging between 30-55mb.

koolpitt commented 8 years ago

Hello,

Please any solution to this issue?

surfjedi commented 8 years ago

I have a solution, ditch ionic! Use something like https://onsen.io/ I have used and it super reliable and fast!

plnkr2015 commented 8 years ago

Ionic team, please help. My app is clocking 150mb++ in android. App has tabs in it. Please help.

christoph-thommen commented 8 years ago

I think my ticket might be related to this one... https://github.com/driftyco/ionic/issues/5743

Same issue here... for long running apps, this behaviour is really a pain :-(

mattgrothmove commented 8 years ago

For those still experiencing issues it may be worth trying the following, it helped us get our app back into a usable state after transitioning between views back and forth as many of you indicate is your test scenario.

The following timeline is from transitioning 5 times back and forth, waiting 5 seconds, and repeating 5 times. You can see there is almost no memory freed up - which indicates something is being left in memory between the views. broken

We started noticing that after going from a list of items, into the item detail view and back out again - over and over, eventually the browser would simply stop responding. The memory usage would climb each successive action, at a steady rate. We investigated a number of possible options (which you should do as well as your results may vary here...check the usual suspects - rogue listeners, use one-time binding when possible. If you are using an extension like Batarang or similar to debug angular in anyway, disable them! They will continually monitor watchers in your app and actually cause fake memory leaks (while active) by holding the watchers in memory and block them from being garbage collected. This will present itself by your watchers count going up and up and up each time you navigate to/from a page and give you a false positive on your leak. So make sure you disable all extensions if possible.

What solution ended up working for us, was to manually clean up directive scopes by watching the elements $destroy event, and manually calling the $destroy event on the child scope. This incredibly simple fix (I am calling it a fix, but its more of a band-aid solution) made our application timeline look a lot more like we expected. The following is how we implemented this. (in both the parent, and child directive)

element.on('$destroy', function () {
     // remove reference to this scope.
     scope.$destroy();
});

Below is the timeline after adding the code snippet to manually $destroy the scope. better

So while this did seem to resolve the issue for us - I still suspect there is something else going on here, and while I will continue to investigate, the solution we came up with worked in a pinch for our timeline. Your mileage may vary. Hope it helps.

sdhull commented 8 years ago

@mattgrothmove very interesting indeed. So you added this to custom directives? I think https://github.com/vitalets/checklist-model is the only custom directive my app is using...

Iodine- commented 8 years ago

@sdhull in my case we are not actually using ionic at all - I do not think this issue is related to Ionic directly but and underlying condition in another component. Which is why I thought to mention what we did.

Ours in an angular application that has several directives which we use ui router to manage states, which sounds similar to what you have in your ionic app. The fix for us was applied in the link method of the directives - but I am sure it could be adapted to be applied directly to a controller with a similar effect. I would be happy to take a look for you, if your project is something you are free to share with me, if not - I suggest you look in to your scopes and ensure they are being destroyed as expected. Would also be worth looking into you use of watchers, it really depends on how your app is implemented!

I know it can be a bit of a crap shoot just trying different things - but honestly, its the only way to really narrow down the root of the issue. Let me know if there is anything else I can do.

sdhull commented 8 years ago

@Iodine- ahhh I see. I guess I could probably do

    $scope.on("$destroy", function(){
      $scope.$destroy();
    });

In the controller... maybe even use $scope.$$childHead and then $$nextSibling to loop through all child scopes and destroy them too. I'll mess with this stuff a bit tomorrow.

Thanks for the help! Hopefully this (or similar) will work for us, though currently the memory issue hasn't (as far as I know) caused any problems for customers...

Iodine- commented 8 years ago

@sdhull I am not positive the code you suggested is going to do much.

I would first start with the following to check if your controller scopes are indeed being destroyed or not. In my case, if I did not manually trigger the scope.destroy on the element.destroy event, then it would not trigger. See if you have a similar issue first.

$scope.$on('$destroy', function() {
     console.log('scope is being destroyed');
});

Second, I would suggest you take a look at your bindings and make sure you've removed any listeners you have on those controllers, especially those watching on $rootScope, which can hold child scopes open. Then go through and use one-time data binding on data that will not likely change in short timeframes (titles, names, phone numbers...etc) by swapping out {{customer.name.given}} with {{::customer.name.given}} to cut down on the watchers there too where possible.

I suggest you change 1 thing at a time, and run a benchmark before you start and after each change you make to check its affect (if any). My test was to navigate to a page and back 5 times, then wait 5 seconds (for timeouts and GC) and repeat that 4 times. Check your timeline and pay attention to your heap size and nodes.

Let me know if there is anything else I can do.

cenobyte321 commented 8 years ago

I've been struggling for some time now with this nasty issue. My app has a view with a PDF, rendered with PDF.js and a couple of videos, rendered with the video tag. In iOS the memory is leaking a lot when going back and forth from this view. After doing this a couple of times the app crashes. I haven't been able to pinpoint exactly where the leak is. If anyone is kind enough to lead me into the right direction for solving this, I would greatly appreciate it.

Here's the project with only the problematic view: https://github.com/cenobyte321/ionic-pdf-video-memory

And a screenshot showing the leak in iOS moments before crashing:

screen shot 2016-04-05 at 3 26 35 pm

Iodine- commented 8 years ago

@cenobyte321 I believe i've tracked down your leak to somewhere in the PDF module you are using. When I benchmark the app without the PDF module the garbage collector can cleanup almost everything. With the module included it doesn't even get 40%. Notice how at the end of the second test the memory usage appears to return to where it was on the first transition, that's what you want to see.

With PDF Module screen shot 2016-04-05 at 3 24 59 pm

Without PDF Module screen shot 2016-04-05 at 3 23 43 pm

Hope that helps.

cenobyte321 commented 8 years ago

@Iodine- Thank you for your reply! Deleting the PDF module did reduce the memory leak, but I believe there's something else going on. By using the script I posted in the repository's readme file the memory leaked to a lesser degree, but after about 400 iterations of the script the app crashed. I know that might be an exaggeration but I could see the app still consuming memory and not releasing it, as shown in the XCode Debugger. The code I used is in the branch 'test-1-no-pdf': https://github.com/cenobyte321/ionic-pdf-video-memory/tree/test-1-no-pdf

Here's the iOS memory graph before the crash: screen shot 2016-04-05 at 7 33 21 pm

EDIT: Just as an additional sanity check I removed the $timeout calls in the view's controller and manually tested the app, it still doesn't free up memory:

screen shot 2016-04-05 at 8 13 56 pm

jrianto commented 8 years ago

My application is a simple pages with LOTS of videos on it. Navigating back and forth crashes the app eventually, before it crashes the app, it will stop loading the videos first.

Can you check on my code and let me know how to destroy everything that is not in the current page from memory? I am new to Ionic, and not sure how to destroy/clean everything from the memory except the current page that is being viewed.

Please check my source at http://flei.ca/FLEI33.zip

If you load it on Google Dev Tools with ionic serve, then navigate back and forth, you will see the video will stop loading eventually. On an actual iPhone after installing the IPA to it, the apps crashes after a while and closes out.

Thank you very much for your help!

g4spow commented 8 years ago

@jrianto I had this issue on my Ionic app that uses embedded video. It seemed to be fixed by adding video.src ="" to the bottom of my code used when you change page/video

`.controller('ProductCtrl', function (Products, $rootScope, $scope, $stateParams, $state, $timeout) { $scope.product = Products[$stateParams.productId];

var video = document.getElementById("myVideo");

// Stop video playing when we go to another page $rootScope.$on('$stateChangeStart', function () { stopVideo(); });

// Go to list of other videos when individual HTML5 video has finished playing angular.element(video).bind('ended', function () { $state.go('app.products'); });

function stopVideo() { video.pause(); video.currentTime = 0; video.src =""; } })`

tslater commented 8 years ago

@jgw96, is there a fix for this, or should we reference another issue, or are you thinking these are all different problems rather than an ionic bug?

jgw96 commented 8 years ago

Hello @tslater ! Thanks for the questions. So, I have done alot of testing on the issues discussed here and in every case it seemed to be related to memory leaks in external libraries/external code. I could not find a good case of a memory leak that seemed to be coming from ionic. Now, this is not me saying that Ionic is written perfectly memory safe, im not sure there is a piece of software out there that is perfectly memory safe, but it does not seem that Ionic is causing the above mentioned major memory leaks or even minor ones. Also, just to be thorough i have tested on older/low-end devices (Nexus 7 2013 and Iphone 5), mid range devices(Moto X 2014) and newer, very performant devices (Nexus 6). Hope this answers your question ! Thansk for using Ionic!

sdhull commented 8 years ago

@jgw96 wow that's very interesting. Did you see my comment from January? Have you tried out the repo I linked to there? Very vanilla implementation and easily reproducible memory problems. Right?

kby799 commented 8 years ago

I have this issue for awhile now, but I thought it was the way I coded so I tried to change my data structure. Still the problem persists, but I notice the problem comes only when I try to go from one view to the next. Also how inconsistent it is, sometimes it is a short climb and some other time it is a big jump that it freezes up the app.

P.S I use tabs template to create this app, and without any manipulation from me. It also shows some kind of memory issue as well, happens between stages.

screen shot 2016-08-04 at 15 57 03
vahidvdn commented 7 years ago

Is there any fix? I have still the same problem.

jrianto commented 7 years ago

@vahidvdn on my case, it is a coding issue as I am very new to ionic. I had to unload the audio/video from memory before loading a new one. So every time my app loads a new video/audio, I am unloading the previously loaded one by doing: videoElement.src =""; // empty source

That did the trick for my app and it never crashed again. So it's not an ionic issue, it's just me not knowing how to code it properly. Hope that helps.

ionitron-bot[bot] commented 6 years ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.