【问题标题】:Garbage collection of a sliced buffer切片缓冲区的垃圾收集
【发布时间】:2015-04-19 15:48:37
【问题描述】:

我在 Node.js 上有服务器端的基于 TCP 的客户端/服务器应用程序。因为 TCP 是一个流,我需要单独的数据包,所以我在每个数据包之前发送两个字节的长度(我猜是常见的做法)。大数据包可以分成几个块(由于 MTU 和其他因素),所以我需要从当前缓冲区中提取我可以提取的每个数据包,并将其余数据包与下一个传入块连接并再次解析。我想出了以下代码:

function parsePackets(data) {
    // join existing buffer contents with new chunk
    var buffer = Buffer.concat([this.buffer, data]);

    var start = 0;
    var end = buffer.length;

    var packets = [];

    while (true) {
        // wait for at least two bytes
        if (end - start < 2) {
            break;
        }

        var length = buffer.readUInt16BE(start);

        // wait until we can read whole packet
        if (end - start < length) {
            break;
        }

        // push packet data as a separate packet
        //var data = new Buffer(length);
        //buffer.copy(data, 0, start, start + length);
        var data = buffer.slice(start, start + length);
        packets.push(data);

        start += length;
    }

    // drop parsed buffer contents, leaving the rest
    //var newBuffer = new Buffer(buffer.length - start);
    //buffer.copy(newBuffer, 0, start, buffer.length);
    //this.buffer = newBuffer;
    this.buffer = buffer.slice(start, buffer.length);

    return packets;
}

我担心的是:切掉的缓冲区内容会被垃圾收集吗?我也在做concat,它返回新的缓冲区(我假设从提供的缓冲区复制内容,这意味着它们可以被垃圾收集)。或者我可能需要使用copy 而不是slice(我在注释行中有一个示例)?

我想要没有内存泄漏的高性能代码,所以我不想复制过多的数据。但目前看来我的应用程序的内存使用量越来越大(我不知道如何确定测试它以及如何检查切掉的内容是否会在某个时候被释放)。

【问题讨论】:

    标签: node.js garbage-collection buffer


    【解决方案1】:

    您的代码中没有累积的缓冲区对象泄漏。

    您不再在代码中保留引用的任何 Buffer 对象都将立即可用于垃圾回收。因此,每次调用parsePackets() 时,都会使用.concat().slice() 创建几个新的缓冲区对象,但每次在this.buffer 实例变量中只保留对一个缓冲区对象的引用,并且每次该缓冲区为替换为一个新的缓冲区(使之前的缓冲区有资格进行垃圾收集)。

    因此,此代码中没有缓冲区对象的累积或累积泄漏。


    为了完整的代码清洁,您需要确保当您完成读取所有数据时,存储在this.buffer 中的最后一个缓冲区对象被类似this.buffer = null; 这样的东西清空,因此不会保留对它的引用即使处理完成。持有此引用的对象本身不需要清理。

    【讨论】:

    • > 已读完所有数据。你是什​​么意思?只要客户端连接,总是有数据要读取。当客户端断开连接时 this.buffer 将与 this 一起被销毁。
    • @bobby - 好的,那部分就可以了。你没有展示整个对象的生命周期是如何管理的,所以我无法知道它是如何被使用的。
    【解决方案2】:

    在 Buffer 中,实际上没有任何内容被切掉。使用切片操作,创建缓冲区的新视图,该视图指向新位置并具有自己的长度。修改原始缓冲区,也会修改切片缓冲区。 根据您的代码,您将仅在 Buffer.concat 中创建一个新缓冲区。然后你将它的几个块分配到数据包中,然后放到 this.buffer 中。所有这些缓冲区都将指向通过 concat 方法创建的原始缓冲区。这是内存效率最高的可行方法,并且在没有 2 个块的内存重叠之前不会产生任何问题。

    【讨论】: