【问题标题】:Memory leak when using deallocate on NSData's UnsafeRawPointer?在 NSData 的 UnsafeRawPointer 上使用 deallocate 时内存泄漏?
【发布时间】:2021-10-09 22:16:15
【问题描述】:

我目前正在测试我从 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' 时它可以正常工作?

【问题讨论】:

    标签: ios swift iphone memory-leaks swift5


    【解决方案1】:
            let pointer = (frameData as NSData).bytes
            pointer.deallocate()
    

    这完全不正确。你没有分配pointer。你不应该释放它。

    没有保证as 调用的返回与原始对象相同。 as 与 C-cast 不同;它不只是重新解释一个指针。它可以创建副本,添加桥接包装器等。在这种情况下,它必须这样做,因为 Data 与 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($0) } // UnsafeRawBufferPointer(start: 0x0000000100142100, count: 16)
    withUnsafeBytes(of: &data) { print($0) } // UnsafeRawBufferPointer(start: 0x0000000100142110, count: 8)
    d.withUnsafeBytes { print($0) } // UnsafeRawBufferPointer(start: 0x00007ffeefbffb50, count: 4)
    print(data.bytes) // 0x0000000100304400
    

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

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

    【讨论】:

    • 谢谢你的详细解释,我已经想到了类似的东西。我的错误是我认为这些是相同的指针,而不是指向相同数据的两个不同的指针。无论如何,我现在使用 UnsafeMutableRawPointer ,所以没有问题了,因为这个指针归我所有。我只是想了解发生了什么,感谢您我现在在做什么 :)
    【解决方案2】:

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

    ARC 基本上是在运行时计算引用,您可以在 Swift Doc 中查看更多信息

    每次创建类的新实例时,ARC 都会分配一块内存来存储有关该实例的信息。此内存保存有关实例类型的信息,以及与该实例关联的任何存储属性的值。

    此外,当不再需要某个实例时,ARC 会释放该实例使用的内存,以便将内存用于其他用途。这可确保类实例在不再需要时不会占用内存空间。

    现在,在我们查看函数的引用计数之前,您需要了解“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
        }
    }
    

    【讨论】:

    • 是的,我知道 UnsafeMutableRawPointer 不提供自动内存管理,但这正是我想要的。我的错误是我认为 NSData.bytes 返回与 dataBuffer 的 UnsafeMutablePointer 相同的指针,据我根据前面的答案理解的情况并非如此。我同意 NSData 返回的 'pointer' 在正常情况下不能被释放,但是因为我使用 'deallocator: .none' 创建了 frameData,所以我必须释放 dataBuffer 来释放内存。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 2010-09-21
    • 2012-01-26
    相关资源
    最近更新 更多