Hi Prasanna,

I have been able to track down all the memory leaks related to reloading a Store with new data. In my chrome memory profiles I didnt see the mentioned 6mb memory increase. For me it went up by 1.7mb after each refresh, which is still a lot, and I'm still looking to see if I can somehow bring this down a bit.

However, with the fixed I have made, when I keep refreshing the store with new data, the memory usage stays exactly the same which should solve the crashes you experienced.



I have slightly adjusted your test case to make it easier to refresh the store several times in a row without having to go back.

Code:
Ext.define('Main', {
    extend: 'Ext.Panel',
    alias: 'widget.main',
    config: {
        fullscreen: true,
        layout: 'card',
        items: [{
            xtype: 'toolbar',
            docked: 'top',
            items: [{
                xtype: 'button',
                text: 'Create Store',
                handler: function() {
                    var store = Ext.data.StoreManager.lookup('test');
                    if (store == undefined) {
                        store = Ext.create('Ext.data.Store', {
                            sorters: 'lastName',
                            storeId: 'test',
                            fields: [ 'firstName', 'lastName' ],
                            syncRemovedRecords: false,
                            grouper: {
                                groupFn : function(record) {
                                    return record.get('lastName')[0];
                                }
                            }
                        });

                        store.setData(TestData);
                        Ext.ComponentQuery.query('main list[name=testList]')[0].setStore(store);
                    }
                    else {
                        console.log('Resetting....');
                        store.setData(TestData);
                    }
                }
            }]
        }, {
            xtype: 'list',
            name: 'testList',
            itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
            grouped : true
        }]
    }
});
I didn't post the data in here, but I basically have a global array called TestData that contains the exact same amount of records as your test case had.

Now when I call setData the total memory usage of the page stays at 6.7mb, no matter how many times I refresh the data.

Also want to note that it is not needed to call removeAll before calling setData anymore.

Unfortunately the fix requires some new configurations and changes to Store that are not possible to do in an override so I had to create a hacky version of the override that you can use till 2.0.1.

Code:
Ext.define('Ext.util.CollectionMemoryFix', {
    override: 'Ext.util.Collection',

    clear: function() {
        var me = this;

        me.length = 0;
        me.items.length = 0;
        me.keys.length = 0;
        me.all.length = 0;
        me.dirtyIndices = true;
        me.indices = {};
        me.map = {};
    },

    removeAt: function(index) {
        var me = this,
            items = me.items,
            keys = me.keys,
            all = me.all,
            item, key;

        if (index < me.length && index >= 0) {
            item = items[index];
            key = keys[index];

            if (typeof key != 'undefined') {
                delete me.map[key];
            }

            Ext.Array.erase(items, index, 1);
            Ext.Array.erase(keys, index, 1);
            Ext.Array.remove(all, item);

            delete me.indices[key];

            me.length--;

            this.dirtyIndices = true;

            return item;
        }

        return false;
    }
});

Ext.define('Ext.data.StoreMemoryFix', {
    override: 'Ext.data.Store',
    
    applyProxy: function(proxy, currentProxy) {
        proxy = Ext.factory(proxy, Ext.data.Proxy, currentProxy, 'proxy');

        if (!proxy && this.getModel()) {
            proxy = this.getModel().getProxy();
        }

        if (!proxy) {
            proxy = new Ext.data.proxy.Memory({
                model: this.getModel()
            });
        }

        if (proxy.isMemoryProxy) {
            this.setSyncRemovedRecords(false);
        }

        return proxy;
    },

    remove: function(records) {
        if (records.isModel) {
            records = [records];
        }

        var me = this,
            sync = false,
            i = 0,
            autoSync = this.getAutoSync(),
            destroyRemovedRecords = true,
            ln = records.length,
            indices = [],
            removed = [],
            isPhantom,
            items = me.data.items,
            record, index, j;

        for (; i < ln; i++) {
            record = records[i];

            if (me.data.contains(record)) {
                isPhantom = (record.phantom === true);

                index = items.indexOf(record);
                if (index !== -1) {
                    removed.push(record);
                    indices.push(index);
                }

                if (!isPhantom && me.getSyncRemovedRecords()) {
                     // don't push phantom records onto removed
                     me.removed.push(record);
                }

                record.unjoin(me);
                me.data.remove(record);

                if (destroyRemovedRecords && !record.stores.length) {
                    record.destroy();
                }

                sync = sync || !isPhantom;
            }
        }

        me.fireEvent('removerecords', me, removed, indices);

        if (autoSync && sync) {
            me.sync();
        }
    },

    doRemoveAll: function(silent) {
        var me = this,
            destroyRemovedRecords = true,
            records = me.data.all.slice(),
            ln = records.length,
            i, record;

        for (i = 0; i < ln; i++) {
            record = records[i];
            record.unjoin(me);
            if (destroyRemovedRecords && !record.stores.length) {
                record.destroy();
            }
        }

        if (me.getSyncRemovedRecords()) {
            me.removed = me.removed.concat(me.data.all);
        }
        me.data.clear();

        if (silent !== true) {
            me.fireEvent('refresh', me, me.data);
        }

        if (me.getAutoSync()) {
            this.sync();
        }
    },

    doDataRefresh: function(store, data, operation) {
        var records = operation.getRecords(),
            me = this;

        if (operation.getAddRecords() !== true) {
            this.removeAll(true);
            // This means we have to fire a clear event though
            me.fireEvent('clear', this);
        }

        if (records && records.length) {
            // Now lets add the records without firing an addrecords event
            me.suspendEvents();
            me.add(records);
            me.resumeEvents();
        }

        this.fireEvent('refresh', this, this.data);
    }
});
Also of note is that this override will slow down the setData call a bit, which is something I'm still working on resolving. Since we are properly removing and destroying every record from the Model cache, there is some extra logic involved with some suboptimal code branches. This will be better in the 2.0.1 release.

Let me know if you have any more questions.

Best,
Tommy