在 NSData 的 UnsafeRawPointer 上使用 deallocate 时内存泄漏?

Memory leak when using deallocate on NSData's UnsafeRawPointer?

我目前正在测试我从 Android 移植到 iOS 的流媒体应用程序,在耐久性测试期间,我注意到一个小的内存泄漏,我终于能够找到它。唯一的问题是我不明白为什么会这样。我对 Swift 比较陌生,所以这可能很明显,但也许有人可以解释一下。您可以在下面找到一个如何重现内存泄漏的示例

private class TestThread: Thread {
    override func main() {
        self.name = "TestThread"
        print("Started TestThread")
        repeat {
            let frameSize = Int.random(in: 523..<63453)
            let dataBuffer = UnsafeMutableRawPointer.allocate(byteCount: frameSize, alignment: MemoryLayout<UInt8>.alignment)
            let frameData = Data(bytesNoCopy: dataBuffer, count: frameSize, deallocator: .none)
            
            // The line below works fine, no memory leak
            //dataBuffer.deallocate()
            
            // Using the code below instead of the line above will result in a memory leak
            let pointer = (frameData as NSData).bytes
            pointer.deallocate()
            
            ThreadingUtil.sleep(ms: 5)
        } while !isCancelled
    }
}

// This is the ThreadingUtil.sleep method
public static func sleep(ms: UInt) {
    usleep(useconds_t(1000 * ms))
}

当您创建并启动 TestThread 时,您可以观察到内存随着时间的推移略有增加,并且只有在您停止线程后才会被垃圾回收。缓冲区内存的大部分似乎都可以被垃圾回收,但仍然有一小部分。我想知道为什么以及为什么当我解除分配原始的 UnsafeMutableRawPointer 'dataBuffer'?

时它工作正常
        let pointer = (frameData as NSData).bytes
        pointer.deallocate()

这是完全错误的。您没有分配 pointer。你不应该取消分配它。

无法保证 as 调用的 return 与原始对象相同。 as 与 C-cast 不同;它不只是重新解释指针。它可以创建副本、添加桥接包装器等。在这种情况下它必须这样做,因为数据与 NSData 不同。它们有不同的类型和不同的结构。

import Foundation
var d = Data([0,1,2,3])
var data = d as NSData
print(type(of: d))  // Data
print(type(of: data)) // _NSInlineData
withUnsafeBytes(of: &d) { print([=11=]) } // UnsafeRawBufferPointer(start: 0x0000000100142100, count: 16)
withUnsafeBytes(of: &data) { print([=11=]) } // UnsafeRawBufferPointer(start: 0x0000000100142110, count: 8)
d.withUnsafeBytes { print([=11=]) } // UnsafeRawBufferPointer(start: 0x00007ffeefbffb50, count: 4)
print(data.bytes) // 0x0000000100304400

它们不是同一个内存。您可以将此示例扩展到更大的数据大小,并且您会看到不同的结果(因为不同大小的块的实现细节不同),但基本点仍然存在。您不能取消分配不属于您的指针。而且您不拥有 .bytes 指针。它恰好指向与 dataBuffer.

相同的值

请注意,在某些情况下,由于实施细节的原因,这可能会起作用。 Data 和 NSData 的实现非常复杂,并且都试图优化很多东西(尤其是很多内联东西),所以 as 转换可能会被优化掉。你无法通过实验解决这个问题。你必须遵守内存管理规则。

swift 中的“垃圾收集器”称为 ARC(自动引用计数),它的工作方式与 java 垃圾收集器有点不同。

ARC 基本上是在运行时计算引用,您可以在 Swift Doc

中查看更多内容

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the type of the instance, together with the values of any stored properties associated with that instance.

Additionally, when an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purposes instead. This ensures that class instances don’t take up space in memory when they’re no longer needed.

现在,在我们查看函数的引用计数之前,您需要了解“UnsafeMutableRawPointer”不提供自动内存管理,因此当您创建“UnsafeMutableRawPointer”时,您需要记住释放它以供参考,您可以阅读“UnsafeMutableRawPointer”here.

我们来看代码:

private class TestThread: Thread {
    override func main() {
        self.name = "TestThread"
        print("Started TestThread")
        repeat {
            let frameSize = Int.random(in: 523..<63453) // frameSize memory allocation: 1
            let dataBuffer = UnsafeMutableRawPointer.allocate(byteCount: frameSize, alignment: MemoryLayout<UInt8>.alignment) // frameSize memory allocation: 0, dataBuffer memory allocation: 1
            let frameData = Data(bytesNoCopy: dataBuffer, count: frameSize, deallocator: .none)  // dataBuffer memory allocation: 1, frameData memory allocation: 1

            // Please note that if this line is commented dataBuffer will stay allocated in the memory until you specifically deallocate it yourself
            //dataBuffer.deallocate() 

            // pointer does not need to be specifically deallocated because as soon as you create it unlike the UnsafeMutableRawPointer class, it is deallocated by the ARC since you are not using it.
            let pointer = (frameData as NSData).bytes // dataBuffer memory allocation: 1, frameData memory allocation: 0
        
            ThreadingUtil.sleep(ms: 5)
        } while !isCancelled
    }
}