1.0.0-beta.26
Vue components are garbage collected
80% of VueComponent instances are retained in memory; even after calling wrapper.destroy after each instance. Heap size continues to grow and grow over time. Garbage collection occurs (either automatically or by force), but only a minimal amount is removed from memory.
Could this be related to #892? I'm experiencing both symptoms: as I re-run my tests with a mocha watcher they get slower (some event timeout) and the heap size keeps growing. Eventually, I get a JavaScript heap out of memory error and the process crashes.
I've also observed that manual GC don't help and a lot of VueComponent objects never get cleaned.
@meis FWIW, we have been having this issue since at least beta 10.
Thanks for the report.
The main issue I found is cached constructors added to component options objects, which results in lots of VueComponent instances that can't be garbage collected. This is likely why VueComponent objects weren't removed even after calling $destroy. I've opened a PR to remove the component that's mounted cached constructor.
I'll also add a cleanUp function that can run in a beforeEach to call $destroy on all mounted instances.
Another issue I found is that some plugins keep references to instances. I'll investigate this further.
Thanks a lot @eddyerburgh.
I've had the chance to quickly test the new version (1.0.0-beta.27) this afternoon. My numbers show an improvement of roughly 50% of the heap growth compared to the last version. This means that, on subsequent runs of the test suite, the heap only increases ~50KB instead of the ~100KB growth I was observing before.
At this point, it's not clear for me which module is responsible for the remaining leaks, but your change certainly helped!
Thanks for this progress! I have done some further testing and found some leaks within vue-router. There are event listeners that hold onto components forever. Trying to determine if there is a quick solution to this particular problem. Not sure if anyone else is also using vue-router.
I have seemed to find this vm.$emit closure as a possible culprit for holding onto vue components forever:
function logEvents (vm, emitted, emittedByOrder) {
var emit = vm.$emit;
vm.$emit = function (name) {
var args = [], len = arguments.length - 1;
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
(emitted[name] || (emitted[name] = [])).push(args);
emittedByOrder.push({ name: name, args: args });
var x = emit.call.apply(emit, [ vm, name ].concat( args ));
console.count(x);
return x;
};
}
There are two memory leaks I can identify:
The cached constructors in the _Ctor added to component options when Vue.extend is called with extend options.
Will need to be solved in Vue Router
mount. I'll look at other ways we could stop cached constructors causing memory leaks.(@mmase logEvents adds properties to the instance, so if the instance is GCd correctly it won't cause a memory leak)
I'm going to close this issue in favor of https://github.com/vuejs/vue-test-utils/issues/892 which is tracking the same issue.
Most helpful comment
Thanks for the report.
The main issue I found is cached constructors added to component options objects, which results in lots of VueComponent instances that can't be garbage collected. This is likely why VueComponent objects weren't removed even after calling
$destroy. I've opened a PR to remove the component that's mounted cached constructor.I'll also add a
cleanUpfunction that can run in abeforeEachto call$destroyon all mounted instances.Another issue I found is that some plugins keep references to instances. I'll investigate this further.