NOTE! You are browsing legacy documentation. For latest visit docs.nativescript.org.

NativeScript Core

Memory Management

The current implementaion of NativeScript for Android utilizes both V8 and Dalvik/ART garbage collectors. Having two garbage collectors may provide challenges for memory management in some scenarios. This articles describes an experimental feature that allows you better fine control for memory management.

Runtime Internal Workings

Every NativeScript for Android app is comprised of two managed heaps - one for Java/Kotlin (Dalvik/ART VM) and another for JavaScript (V8). Every time when an app executes JavaScript code as follows

var file = new java.io.File("somefile");

it creates two objects - one in the JavaScript heap and another in the Java heap. The JavaScript object serves only as a proxy to the actual Java object. Thus the size of the JavaScript object is very small. Suppose the app has to execute the following line

let success = file.delete();

The only information we need is some sort of id in order to find the corresponding Java object and call delete() on it. Currently we use int32 for the id. So practically, we can think of file as

let file = { javaObjectId: 123 };

if the actual id has value 123. In other worlds, creating JavaScript objects is very cheap.

Having said that, this could lead to some scenarios where there are unreachable JavaScript objects (e.g. effective garbage) for which the corresponding Java/Kotlin objects hold a lot of memory (think of Bitmap, String, StringBuilder, I/O buffers, etc.). But because there is little or no memory pressure in the JavaScript heap there is no reason for V8 GC to kick off. Therefore NativeScript for Android runtime doesn't have the chance to release the Java/Kotlin object handles (if any). This could cause OutOfMemoryError.

markingMode: "none"

By default during a V8 GC pass the Android runtime performs an automatic traversal of alive JavaScript instances (a routing called MarkReachableObjects). In apps which create huge JavaScript instances graphs however this can cause significant delays in garbage collections, leading to unpleasant blocks of the UI thread.

Basically, this algorithm makes sure that no Java/Kotlin instances will be prematurely collected by the Android Runtime GC, while they are still being used by the JavaScript side. JavaScript code, however, can be structured in such a way that this additional step becomes unneeded. The only case when it is required is when there are no other references to the Java/Kotlin instance than the scope and the scope is the only thing that takes care to keep it alive.

There's a app/package.json option which apps can set in order to disable MarkReachableObjects:

{
  "android": {
    "markingMode": "none"
  }
}

The code inside @nativescript/core and all plugins published by the NativeScript Team (since version 5.1.0) are written in such a way, that it does not depend on the scope to keep those Java/Kotlin instances alive. This makes apps using these plugins fully compatible with the much more performant markingMode: "none" option. More information on markingMode:none.

WARNING: Enabling this option if the JavaScript code dealing with native objects (either in the application or any plugins you are using) does not correctly take care of the lifetime of Java/Kotlin instances may cause unexpected and unpredictable crashes of the application. This would be caused by Java/Kotlin instances being prematurely collected. Use caution when enabling it and make sure to thoroughly test your apps with different memory constrains and devices! The errors generated in such cases look like this: Error: com.tns.NativeScriptException: Attempt to use cleared object reference id=<some-object-id-number>

Syncronizing Garabage Collectors

In order to mitigate such issues we provide an experimental feature which tries to synchronize both garbage collectors. One option is to trigger GC for the JavaScript heap so it collect all unreachable JavaScript objects which can hold reference to potentially large Java/Kotlin objects. There are three possible approaches.

Explicitly Triggering GC

The default Android project template is configured to expose global gc() function. You can call gc() whenever you consider there is effective garbage in the JavaScript heap that pottentially hold reference to large Java/Kotlin objects. For example, calling gc() in Activity's onDestroy() might be a good option if the activity in question a root of a large object hierarchy.

Using gcThrottleTime parameter

You can use gcThrottleTime parameter to notify NativeScript runtime to trigger GC in the JavaScript heap during GC in the Java heap. The value of this parameter is measured in milliseconds. The value of 0 (zero) disables this parameter.

{
  "android": {
    "v8Flags": "--expose_gc",
    "gcThrottleTime": 500
  }
}

Here is how it works. When a GC happens for the Java heap NativeScript runtime will check if it has to notify the V8 engine that it has to perform full garbage collection. The check is throttled using the time defined by gcThrottleTime parameter. For the example above, if the runtime has not notified the V8 engine in the last 500 milliseconds it will send a notification to the V8 engine to it can perform full GC. It is not guaranteed that the GC will be performed on the next JavaScript statement. The current implementation triggers GC the every second Java/Kotlin method invocation or on every Java/Kotlin object allocation. It is recommended to tune this parameter depending on the memory allocation pattern of your application.

Using memoryCheckInterval and freeMemoryRatio parameters

The previous strategy (using gcThrottleTime parameter) may not work for all scenarios. For example, if your app allocates large Java/Kotlin objects in a loop then using gcThrottleTime parameter may not be enough to prevent your app from OutOfMemoryError. For such scenario we provide the pair memoryCheckInterval and freeMemoryRatio which may help. The value of memoryCheckInterval parameter is measured in milliseconds and the value of 0 (zero) disables this approach. The value of freeMemoryRatio is measured in percentage (from 0.0 to 1.0) where the value of 0.0 disables this approach.

{
  "android": {
    "v8Flags": "--expose_gc",
    "memoryCheckInterval": 500,
    "freeMemoryRatio": 0.50
  }
}

Here is how it works. NativeScript runtime starts a new thread which monitors the avaivble memory. It calculates the ratio of free/total memory on a regular basis using the value specified with memoryCheckInterval. If the calculated ratio is less than freeMemoryRatio it notifies the V8 engine that it has to perform full GC. It is not guaranteed that the GC will be performed on the next JavaScript statement. The current implementation triggers GC the every second Java/Kotlin method invocation or on every Java/Kotlin object allocation. It is recommended to tune this parameter depending on the memory allocation pattern of your application.

Best Practices

The described approaches are not mutually exclusive. You can combine them as it may fit. Here are a few guidelines that you may find useful:

  • Reuse object whenever possible
  • Use object pools
  • Avoid using large I/O buffers. In many cases 64KB is optimal size
  • Avoid loading large images
  • Avoid allocating many large objects at once
  • Avoid allocating objects in loops

Be cautious when you write closures as it often hard to observe object retentions along all the reference chain.