Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue memory leak when rendered components are removed

Tags:

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>
like image 691
Sven Hakvoort Avatar asked Dec 23 '19 11:12

Sven Hakvoort


People also ask

What are some common sources of memory leaks in Vue apps?

A common source of memory leaks are incorrect destructing and unhandled contexts. If you call a 3rd-party component constructor inside a component initialization hook, check that you correctly unregister everything in the beforeDestroy hook. Another potential source of memory wastefulness is a Vuex state.

How will you solve common causes of memory leaks in VUE JS applications?

Resolving the Memory Leak In the above example, we can use our hide() method to do some clean up and solve the memory leak prior to removing the select from the DOM. To accomplish this, we will keep a property in our Vue instance's data object and we will use the Choices API's destroy() method to perform the clean up.

How do I check my Vue memory leaks?

js Memory Leak Example. To see the memory leak occur, open the Chrome Task Manager and then click the hide/show button 50 or so times. You should notice the memory continue to increase and not be reclaimed.

How can we avoid memory leak?

To avoid memory leaks, memory allocated on heap should always be freed when no longer needed.


1 Answers

I had a similar issue. The object that I passed on through a property to the next component was complex and large in my case, I do not know if this is also the case for you?

My issue was solved by changing the way of passing my object. By changing the property to a number, in my case an ID, I was able to retrieve my object in the component where the property is used (based on the ID). As a result, I did not have to pass on the entire object repeatedly. For some reason passing large objects as data props is not working as it should and causes strange behavior...

In your case it maybe helps when you don't pass the 'tab' property directly to your component, but rather an index of the location of this element in the store and then fetching it directly from the store within your component.

So you need to change your v-for to:

<b-tab v-for="(tab, index) in tabs" @click="doSomething" @change="doSomething">
    <keep-alive>
        <TabComponent :tabIndex="index"></TabComponent>
    </keep-alive>
</b-tab>

And in your TabComponent:

props: {
    tabIndex: Number,
},
data: function () {
    return {
        tab: this.$store.state.tabs[this.tabIndex]
    }
}

Of course this principle would also need to be applied to any child components doing the same thing to prevent any memory leaks in child components which would obviously also impact the parents. Hopefully I could help you :)

like image 160
Kimberley Avatar answered Sep 30 '22 20:09

Kimberley