2

I found writing huge amount of data to Realm in iOS causes out of memory and crash. After days of investigation, I found that Realm does not release unused objects in a List. I ran the following example:

class LeakTestList : Object{
    var items = List<LeakTestItem>()
}

class LeakTestItem : Object{
    @objc dynamic var data = 0
}


func leakTest()
{
    guard let realm = try? Realm() else
    {
        return
    }
    let leakTestList = LeakTestList()
    leakTestList.items.append(objectsIn: (0..<10000).map{LeakTestItem(value: ["data":$0])})
    try? realm.write {
        realm.add(leakTestList)
    }
}

After leakTest() return, I got the following memory profile:

Realm memory leak test

LeakTestList has already gone but all the items remains in memory. This cause out of memory when I tried to write a lot of list items even divided into multiple short enough lists. Is this a bug from Realm or is there anything I can do to solve the problem?

Ricky Mo
  • 6,285
  • 1
  • 14
  • 30
  • Not the downvoter but I am not sure I am following this question. A bunch of Realm objects are created in memory and the assumption there's a memory leak? Where else would they be? Realm objects are lazily *loaded* which isn't that code. – Jay Jan 10 '20 at 18:17
  • 1
    @Jay A bunch of Realm objects are created, added to a List of another Realm object. When the function return, there are no more references to the objects. Even the object which contains the List is already deallocated, the list items are not deallocated. Let say I have only 2GB of RAM and want to write a List of realm obejcts which takes up 3GB of RAM. It is impossible to write in one round, but still not possible even if I chop up the list into smaller pieces because the items never deallocate. – Ricky Mo Jan 11 '20 at 09:03
  • I am using the default realm which write to a file on disk, not pure in-memory realm. I recently found that I put the test function within viewDidLoad and the list item realm obejcts stay allocated throughout viewDidLoad but get deallocated in viewDidAppear. I just want to know when will the unreferenced realm objects get deallocated exactly as I have to write a lot of them to disk. – Ricky Mo Jan 11 '20 at 09:11
  • We've been caught by this a few times so I want to metion the size of the file being written by that code is much larger than it needs to be. On one hand it's generally better to have a single write transaction that writes a bunch of data than a bunch of write transactions that add smaller data. On the other hand a single write will create a *much* larger file but is faster. See @bdash answer to [this question](https://stackoverflow.com/questions/46228149/unexpectedly-large-realm-file-size). – Jay Jan 11 '20 at 16:23
  • Oh - and a followup question; You said *When the function return, there are no more references to the objects* but then said *the list items are not deallocated*. I ran your code and when the leakTest function concludes, none of the objects - either the leakTestList nor the LeakTestItems are still in memory. Are you saying there are no references to those objects but they are still in memory *after* the leakTest() function completes? – Jay Jan 11 '20 at 16:36
  • @Jay I edited the screenshot which shows where I call the function and set the break point. – Ricky Mo Jan 13 '20 at 02:16

2 Answers2

1

Referring to @Jay 's answer, I am able to remove the memory footprint of the realm objects from the memory monitor, but the memory usage stay the same until viewDidLoad() end. After more digging, turns out the key idea I missed was to wrap everything in autoreleasepool. Referring to this article : https://realm.io/docs/cookbook/swift/object-to-background/

func leakTest()
{
    autoreleasepool {
        guard let realm = try? Realm() else
        {
            return
        }

        let leakTestList = LeakTestList()

        try? realm.write {
            realm.add(leakTestList)
        }

        try? realm.write {
            leakTestList.items.append(objectsIn: (0..<10000).map{LeakTestItem(value: ["data":$0])})
        }
    }
}
Ricky Mo
  • 6,285
  • 1
  • 14
  • 30
  • hi, did you confirm that auto release pool works? I was working in a stress test similar to your code and i did try auto release but i still get a crash. [link](https://www.mongodb.com/community/forums/t/already-in-write-transaction-at-stress-test/140062) – João Serra Jan 06 '22 at 10:43
0

We find that establishing the object in realm and then appending to that object in smaller chunks seems to not only help with in-memory issues but also significantly reduces the file size.

Our project had to read and process files that were 50Gb+ and we found that writing about 1000 objects at a time seemed to be the balance point between speed, file size and memory. Your milage may vary.

I refactored your code and added a couple of for loops to show what's going on but try this out and see if the memory footprint is better by comparison.

This writes 10 total items in smaller chunks which, as mentioned in my comment, reduces the overall file size. for your example the outside loop would be 40 and the inside would be 1000.

let leakTestList = LeakTestList()

try? realm.write {
    realm.add(leakTestList)
}

var index = 0
for _ in 0..<2 {
    var myItems = [LeakTestItem]()
    for _ in 0..<5 {
        let item = LeakTestItem( value: ["data": index] )
        myItems.append(item)
        index += 1
    }

    try? realm.write {
        leakTestList.items.append(objectsIn: myItems)
    }
}
Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thank you for your reply. I tested this code. I call the function in `viewDidLoad()`. The realm objects do disappear from the memory profile after the function return, but the memory usage keep rising throughout the function and remains unchanged at high usage until it hit `viewWillAppear()` the memory usage goes down. I wonder if realm only clean up when the thread idle or something else? – Ricky Mo Jan 13 '20 at 03:27
  • I solved my problem by recursively dispatching the write operations into a dispatch queue after the previous write operation has completed so the memory is completely deallocated before the next dispatched item start. – Ricky Mo Jan 13 '20 at 08:39
  • Hi Jay, you think you could please take a look at this https://stackoverflow.com/questions/71167182/swiftui-list-memory-issue-images-not-deallocating-from-ram-causing-crash – MoLoW Feb 18 '22 at 02:29