Categories
javascript memory-leaks memory-management vue.js vuex

Vue memory leak when rendered components are removed

Within a Vue application I am encountering a memory leak, the scenario in which it occurs is as follows:

  • We have a component which is rendered within a v-for which contains many child components
  • When the corresponding element is removed from the array the v-for rerenders these components and correctly removes the component that corresponds to the element removed from the array.

However the allocated memory is never freed, the application starts out with ~30-40 MB of RAM usage, which increases to 200MB RAM when the v-for is rendered (and eventually goes up to more than 1GB and crashes the browser when more elements are added or when switching). When the element is removed it stays steadily at 200MB (even when manually garbage collecting), so it seems like something it retaining my component.

I have tried locating the issue with heap snapshots but it only shows a child component as retainer. I cannot locate what is causing this component to not be garbage collected. I have tried unsubscribing all event listeners on the root with this.$root.off however this does not seem to help at all…

The code itself is condifential so I cannot just share it, however if a bit of code is necessary to understand the issue please let me know, so i can provide a replicated example.

Does anyone have any ideas how I can solve this issue or has any ideas how to locate the cause of this memory leak?

UPDATE

This is the component which renders the components in the v-for:

<template>
<b-tabs card class="tabMenu" v-model="index">
<b-tab v-for="(tab) in tabs" @click="doSomething" @change="doSomething">
<TabComponent :tab="tab"></TabComponent>
</b-tab>
</b-tabs>
</template>
<script>
import TabComponent from "./TabComponent";
export default {
components: {
TabComponent,
},
created: function () {
this.$root.$on("addTab", this.addTab);
},
data: function () {
return {
tabs: this.$store.state.tabs,
}
},
beforeDestroy: function(){
this.$root.$off("addTab");
},
methods: {
addTab(tab) {
this.$store.commit("addTab", {tab: tab});
},
}
};
</script>

And the tab component it renders:

<template>
<div @mousedown.stop>
<!-- Other components are loaded here but not relevant -->
<div>
<div v-show="conditionA">
<resize-observer @notify="doSomething" v-if="conditionC"></resize-observer>
<!-- This component renders many SVG elements which can be found in the heapsnapshot as DetachedSvgElements when the parent is not present anymore -->
<VisualizationComponent v-show="conditionD"
:tab="tab"></VisualizationComponent>
</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
props: {
tab: TabObject,
},
data: function () {
return {
}
},
watch: {
// Some watchers
},
mounted: function () {
this.$nextTick(function () {
// Do some calculations
this.$root.$emit("updateSomething");
});
},
created: function(){
this.$root.$on("listen", this.doSomething);
// And listen to more events
},
beforeDestroy: function(){
this.$root.$off("listen");
// And unsubscribe all others
},
computed: {
// Quite a lot of computed props
},
methods: {
// And also many methods for data processing
}
}
</script>