切片缓冲区的垃圾收集

Garbage collection of a sliced buffer

我有基于 TCP 的 client/server 应用程序,服务器端在 Node.js。因为 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 那个 returns 新缓冲区(我假设从提供的缓冲区复制内容,这意味着它们可以被垃圾收集)。或者我可能需要使用 copy 而不是 slice (我在注释行中有一个示例)?

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

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

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

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

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


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