While working on a Vue application, I have come across a memory leak issue. The situation where it occurs is as described below:
- In the application, there is a component that is rendered within a v-for loop and contains several child components.
- When an element corresponding to the component is removed from the array, the v-for loop re-renders these components and correctly removes the component associated with the removed element.
However, the allocated memory is not released. Initially, the application uses around 30-40 MB of RAM, but this increases to 200MB when the v-for loop is rendered (and goes up to over 1GB, eventually crashing the browser, especially when adding more elements or switching). Even after removing the element, the memory remains steady at 200MB (even after manual garbage collection), indicating that something is retaining my component.
I have tried using heap snapshots to identify the problem, but they only show a child component as a retainer. I am unable to pinpoint what is preventing this component from being garbage collected. Additionally, unsubscribing all event listeners on the root with this.$root.off
did not resolve the issue...
The code itself is confidential, so I cannot share it directly. However, if some code snippet is needed to understand the problem, please let me know so I can provide a simplified example.
Is there anyone who has suggestions on how to solve this issue or ideas on how to discover the source of this memory leak?
UPDATE
This is the main component rendering components in the v-for loop:
<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 here is the tab component being rendered:
<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>