Kotlin Native. New Memory management Model

Anna Zharkova
Google Developer Experts
4 min readDec 6, 2021

--

In previous posts we described how to use the native mechanism of concurrency with and without Coroutines. Now let’s talk about the New Memory Management Model.

On August 31 Jetbrains company presented new memory management model for Kotlin/Native. The main focus was made at thread-safety, safe context and data sharing between threads, memory leaks fixing and working without need to use special annotations. Also there are several Coroutines improvements. Since now it is possible to switch between contexts without any need of freezing. All these updates are supported by Ktor in new versions.

Let’s summarize what’s new in the suggested memory model:

1.Multithreading without freeze(). It was claimed, that we can remove all freeze() blocks from our code, even from the background workers, and switch between contexts and thread without any blockers and problems.

2.AtomicReferences/ FreezableAtomicReference don’t produce any leaks.

3.No need of ShareImmutable when use globals constants.

4.The Producer of Worker.execute doesn’t return isolated graph of dependencies any more.

Also there are several nuances and side effects:

  1. We still have to use freeze() with Atomic references. To deal with it use FreezableAtomicReference instead. Also we can use AtomicRef from atomicfu.

2. All global constants are initiated lazily. In previous version all globals were initialized immediately at start. In new version use EagerInitialization to keep such behaviour.

3. There is no guarantee that suspend function will return the completion handler into the Main Thread. So we need to wrap it in DispatchQueue.main.async{…} for iOS.

4.Deinit for Swift/ObjC objects could be called in other thread.

Speaking about Coroutines, there are also some improvements and changes. You can check them in special version branch with new memory model support:

1.We can work with Workers, Channels and Flows without freeze. In contrast of native-mt version all the content of Channel could be unexpectedly frozen.

2. Dispatchers.Default now is bounded to the Global queue.

3. newSingleThreadContext and newFixedThreadPoolContext could be used to create new Coroutine Dispatcher with support of the pool for one or several Workers.

4.Dispatchers.Main is bound with Dispatch Main Queue for Darwin and special Worker for other Native platforms. It is recommended not to use it for Unit testing, because nothing will be called in main thread queue.

So, there a bunch of different improvements, changes, nuances, also with some performance bugs and problems. All of them are known and described in documentation. At moment it is just a preview version, not the Alpha release and the Jetbrains command still improve it and develop.

Well, let’s apply all new features to our code sample.
At first, we are going to install correct versions for Coroutines and Kotlin:

Add correct dependency from Coroutines:

Important! You have to install Xcode 12.5 or newer. It is a minimal compatible with 1.6.0-M1–139 version. If you already have installed more than one version, you need to switch to the correct variant with xcode-select. Than close Kotlin Multiplatform project and call Invalidate cache and Restart.

Now we are going to remove all freeze() blocks from non-coroutine code:

Remove all freeze() from all the parameters we use in NSUrlSession. Remember, we deal with the native network client:

Also we need to switch from AtomicReference to FreezableAtomicReference:

Apply changes to code:

Our code is clean and fresh, our app is flying, despite GC still work not perfectly.
Now let’s tweak our Coroutine sample:

We are going to use standard Dispatchers, that are available by default. In order to check a GlobalQueue we need to output an information about the Coroutine Context from ioDispatcher.

Now we remove all the freeze() from Flows and Channels:

It work nice and fast. Do not forget to send an answer in main thread:

Important! In order to prevent memory leaks in iOS side, it will be useful to wrap the blocks with a lot Swift/ObjC in autoreleasepool

Let’s check some cases. We are going to make a request from the MainScope and specify some other background Dispatcher with newSingleThreadContext:

Works without any troubles. It seems that the new memory management model will be perfect solution for all developers and simplify our work.

But! It could be some problems with libraries that don’t support new-mm at moment. In some cases there could be InvalidMutabilityException or FreezingException.
In order to deal with them and Kotlin 1.6.0-M1 or newer we have to disable embedded freezing:

More info read here: https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md

Some pieces of sample:
https://github.com/anioutkazharkova/kotlin_native_network_client/tree/feature/1.6-kn/sample

--

--

Anna Zharkova
Google Developer Experts

My name is Anna Zharkova, I'm from Barnaul. Lead Mobile developer, more than 7 years of experience. I develop both native and cross platform. Kotlin GDE