ChartJs 内存泄漏 |垃圾收集在渲染后不清除图表对象或数组

ChartJs Memory Leak | Garbage Collection does not clean Chart Object or Arrays after render

我正在做一个项目,我必须使用 chartJs 创建大量图表。我注意到当图表数量增加到 1000 个时,我的页面一直崩溃(会因机器内存而异)。

在使用 chrome devtools 进一步探索问题后,似乎一旦创建图表,它们就不会被垃圾收集。每个图表对象和对象创建的数组(标签数组、数据数组中的数据数组)都保留在内存中,并且它们保留在内存中,不会被垃圾收集。由于当页面中的图表数量在达到内存限制后页面崩溃一段时间后达到峰值时,它们不会被垃圾收集。

如何修复此内存泄漏或触发垃圾回收?

编辑:请注意,我不想删除生成的图表。

JSFiddle 更清晰的视图

图表生成脚本如下

var COLORS = ["#265b62", "#FF6361", "#FFA600", "#66C2A4", "#EF3B2C",
        "#67000D", "#349866", "#A05195", "#78C679", "#2B8CBE",
        "#8C96C6", "#373F52", "#810F7C", "#BC5090", "#FF3D67",
        "#D5B029", "#CD9B9A", "#EC7014", "#665191", "#003F5C"];

    function getLabels(numberOfLabel) {
        var labels = [];

        for (var i=1; i<= numberOfLabel; i++) {
            labels.push('Category '+i);
        }

        return labels;
    }

    function getDataSeries(labels, numberOfSeries) {

        var dataSeries = [];
        for (var i=1; i<= numberOfSeries; i++) {
            dataSeries.push(getDataObj(i, labels.length));
        }

        return dataSeries;
    }

    function getDataObj(count, numberOfLabel) {
        var seriesLabel = 'Dataset ' + count;
        var colorName = COLORS[count % COLORS.length];

        dataArray = [];

        for (var i=1; i<= numberOfLabel; i++) {
            dataArray.push(randomScalingFactor());
        }

        var dataObj = {
            label: seriesLabel,
            backgroundColor: colorName,
            borderColor: colorName,
            data: dataArray,
            fill: false,
        };

        return dataObj;
    }

    function getConfig(chartCount, dataPoints, barOrLineCounts) {
        var chartLabels = getLabels(dataPoints);
        var dataSeries = getDataSeries(chartLabels, barOrLineCounts);

        var chartTitle = 'Chart.js Line Chart Count ' + chartCount;
        var chartType = 'line';
        var config = {
            type: chartType,
            data: {
                labels: chartLabels,
                datasets: dataSeries
            },
            options: {
                // ===============performance tweaks==========================================
                parsing: false,
                spanGaps: true,
                elements: {
                    line: {
                        tension: 0, // disables bezier curves for line datasets
                        fill: false,
                        stepped: false,
                        borderDash: []
                    },
                    point: {
                        radius: 2 // default = 3, set 0 to disable and gain performance
                    }
                },
                animation: false,
                // ===============performance tweaks==========================================
                responsive: true,
                title: {
                    display: true,
                    text: chartTitle,
                },
                tooltips: {
                    mode: 'index',
                    intersect: false,
                },
                hover: {
                    mode: 'nearest',
                    intersect: true
                },
                scales: {
                    xAxes: [{
                        display: true,
                        ticks: {
                            autoSkip: false,
                            maxRotation: 45,
                            minRotation: 45,
                            sampleSize: 35
                        },
                        stacked: chartType === 'bar',
                        scaleLabel: {
                            display: true,
                            labelString: 'Month'
                        }
                    }],
                    yAxes: [{
                        display: true,
                        ticks: {
                            // setting min and max will gain performance as chartjs wont have to compute this.
                            min: 0,
                            max: 100
                        },
                        scaleLabel: {
                            display: true,
                            labelString: 'Value'
                        }
                    }]
                }
            }
        };

        return config;
    }


    function generateCharts(chartCount, dataPoints, barOrLineCounts) {
        for (var i=1; i<= chartCount; i++) {
            var ctx = document.getElementById('canvas'+i).getContext('2d');

            var myLine = new Chart(ctx, getConfig(i, dataPoints, barOrLineCounts));
        }
    }

    window.onload = function() {

                var chartCount = 1;
        var dataPoints= 20;
        var barOrLineCounts = 10;


        $('#chartType').val("${chartType}");

        generateCharts(chartCount, dataPoints, barOrLineCounts);
    };

    var xCount =  2;
    function addChart() {
        var chartCount = 1;
        var dataPoints= 20;
        var barOrLineCounts = 10;

        var ctx = document.getElementById('canvas'+xCount).getContext('2d');
        var myLine = new Chart(ctx, getConfig(xCount, dataPoints, barOrLineCounts));

        xCount = xCount + 1;
    }

    function randomScalingFactor() {
        return Math.random() * 100;
    }

测试用例的示例 HTML。

<div class="panel-body">
   <button id="addChart" onclick="addChart()">Add Chart</button><br><br>
   <div id="container">
      <div id="chart1" class="col-sm-12">
         <canvas id="canvas1">
         </canvas>
      </div>
      <div id="chart2" class="col-sm-12">
         <canvas id="canvas2">
         </canvas>
      </div>
      <div id="chart3" class="col-sm-12">
         <canvas id="canvas3">
         </canvas>
      </div>
      <div id="chart4" class="col-sm-12">
         <canvas id="canvas4">
         </canvas>
      </div>
      <div id="chart5" class="col-sm-12">
         <canvas id="canvas4">
         </canvas>
      </div>
...
...
...
      <div id="chart17" class="col-sm-12">
         <canvas id="canvas17">
         </canvas>
      </div>
      <div id="chart18" class="col-sm-12">
         <canvas id="canvas18">
         </canvas>
      </div>
      <div id="chart19" class="col-sm-12">
         <canvas id="canvas19">
         </canvas>
      </div>
      <div id="chart20" class="col-sm-12">
         <canvas id="canvas20">
         </canvas>
      </div>
   </div>
</div>

为了让您的图表成为 garbage-collected,您必须确保没有对它们的剩余引用。

使用chartInstance.destroy()清理Chart.js对图表对象的内部引用:documentation

当您调用 new Chart(...) 时,您必须将该引用存储在某处(例如在数组中),并在完成后调用 .destroy()。不要忘记也从数组中删除引用。