Java side garbage collection seems to have something retained from each FS writeFile. And will OOM after a number of writes.
TNS 2.5.x
No
Yes:
S: File saving error Error: java.lang.OutOfMemoryError
S: java.nio.charset.CharsetEncoderICU.getArray(CharsetEncoderICU.java:220)
S: java.nio.charset.CharsetEncoderICU.encodeLoop(CharsetEncoderICU.java:162)
S: java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:368)
S: java.io.OutputStreamWriter.convert(OutputStreamWriter.java:178)
S: java.io.OutputStreamWriter.write(OutputStreamWriter.java:324)
S: java.io.Writer.write(Writer.java:141)
S: com.tns.Runtime.callJSMethodNative(Native Method)
S: com.tns.Runtime.dispatchCallJSMethodNative(Runtime.java:1021)
S: com.tns.Runtime.callJSMethodImpl(Runtime.java:903)
S: com.tns.Runtime.callJSMethod(Runtime.java:890)
S: com.tns.Runtime.callJSMethod(Runtime.java:874)
S: com.tns.Runtime.callJSMethod(Runtime.java:866)
S: com.tns.gen.java.lang.Runnable.run(Runnable.java:10)
S: android.os.Handler.handleCallback(Handler.java:733)
S: android.os.Handler.dispatchMessage(Handler.java:95)
S: android.os.Looper.loop(Looper.java:136)
S: android.app.ActivityThread.main(ActivityThread.java:5001)
S: java.lang.reflect.Method.invokeNative(Native Method)
S: java.lang.reflect.Method.invoke(Method.java:515)
S: com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
S: com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
S: dalvik.system.NativeStart.main(Native Method)
You need a really large text file. In this case the file it 6.5 megs of JSON encode data. (This is not a typo it really is 6.5 megs of text data that I'm sending to the file system...
private writeData(fileName:string, data:any):void {
this.writeText(fileName, JSON.stringify({data: data, index: data._index, isValid: true}));
}
private writeText(fileName:string, data:string):void {
let appPath = fs.knownFolders.documents().path + "/";
let writeFile = fs.File.fromPath(appPath + fileName);
writeFile.writeTextSync(data, function (err) {
console.error( "File saving error", err, err.stack);
});
// Clean up
writeFile = null; data = null;
}
The code is trivially simple; I pass a filename, and a data object.
The WriteData, just creates a JSON version of the object (these are POD's, nothing weird).
It tells writeText to write it out; and we are done.
However, after this occurs about a dozen times; it will start throwing the above error, by adding memory logging I can see my memory rapidly deplete each time I run this code path that calls this function. If I add a "return" at the first line in the writeText, my memory only slowly increases and then gets GC'd at a later point...
Just as a Data point. I am running a manual global.GC() before I check memory usage which occurs every time I re-navigate back to the main screen for it to show me the current app memory usage. So at least two manual 'GC` are called before it saves again. So the JS side should have nothing retained by the time I save that file again...
Hey @NathanaelA thanks for reporting these observations.
I wasn't able to reproduce an OOM crash in an isolated hello-world app however. Following is the code I used:
function createViewModel() {
var viewModel = new Observable();
viewModel.counter = 42;
viewModel.message = getMessage(viewModel.counter);
viewModel.onTap = function() {
writeData("mytextfile.txt", genText(6400000));
gc();
java.lang.System.gc();
}
return viewModel;
}
function genText(length) {
var text = "";
for (var i = 0; i < length; i++) {
text += "A";
}
return text;
}
function writeData(fileName, data) {
data._index = 1;
writeText(fileName, JSON.stringify({data: data, index: data._index, isValid: true}));
}
function writeText(fileName, data) {
let appPath = fs.knownFolders.documents().path + "/";
let writeFile = fs.File.fromPath(appPath + fileName);
writeFile.writeTextSync(data, function (err) {
console.error( "File saving error", err, err.stack);
});
// Clean up
writeFile = null; data = null;
}
I wasn't using java.lang.System.gc(); --- That might be the difference. I totally forgot about that, and the error was on the Java side; so maybe I need to add that code to my writetext function.
@NathanaelA that was actually a left over of what I've been trying out, let me know if it does solve anything.
@NathanaelA do you find this to still be an issue? I haven't explicitly tested garbage collection since.
Sorry, I have not had a chance to retest this in 3.0 yet. The project I have uses a lot of plugins that haven't been migrated to 3.0 yet. And I haven't added that line to the code; however, I will be doing work with this project tomorrow; so I should be able to see if the java.gc will fix it.
I'm on NS / TNS 3.0.3 and am also running into oom errors, but in my case am using images with their src attribute bound to an imageSource retrieved from disk. The following is a stripped down version of a component and its template that can be navigated to and from via angular router. If I navigate to and from the page about 5 times I get the oom error. The listview only has two individuals in my current case => 2 images. If I comment out the Image tag I don't get the out of memory error:
Error: java.lang.OutOfMemoryError: Failed to allocated a 28800012 byte allocation with 8449484 free bytes and 8MB until OOM
<ListView [items]="individuals">
<ng-template let-item="item">
<Image stretch="fill" [src]="getImage(item)">
</Image>
</ng-template>
</ListView>
getImage(individual: Individual) {
return imageSource.fromFile(fs.path.join(this.folder.path, individual.imageName));
}
ngOnDestroy() {
GC();
java.lang.System.gc();
}
Maybe I need to set something in the DOM to null to get garbage collection to throw away the imageSources? I was thinking that by the time ngOnDestroy hits the images should be marked for gc.
On a somewhat related note, my app has lag spikes sometimes on router navigations which I believe are caused by garbage collection. It only seems to happen on android, ios is fine. These lag spikes can occur even if I don't call GC(); or java.lang.System.gc(); in ngOnDestroy. Any tips or thoughts on why this might be happening?
Also if I change my getImage function to just return a string containing a url to a .jpg I don't get oom errors. It seems like the image is then cached though because there is only a delay in retrieving the photo the first time I navigate to the page.
My requirements are that the images are loaded from disk and were previously saved there via a sync routine. This is why I'm using imageSource.
Also if I change my getImage function to just return a string containing a url to a .jpg I don't get oom errors. It seems like the image is then cached though because there is only a delay in retrieving the photo the first time I navigate to the page.
Hey @jeffswitzer you are quite right - the detailed explanation why is this happening is described https://docs.nativescript.org/ui/images-optimisations
In some cases when working with multiple large images on devices with low memory, an Out Of Memory exception can occur. To prevent that scenario, in NativeScript 2.5.x and above using the
srcproperty in Android will internally load the Bitmap in Java. Bitmap memory stays in Java world and reclaims once the Bitmap is no longer in use (e.g. there is no need for the Javascript object to be collected).This way Bitmap memory management is not an issue.In contrast, when using ImageSource or Base64 encoded string, the Bitmap is transferred to Javascript, so it will be released when Javascript object reclaims. Javascript garbage collection happens less frequently than Java garbage collection which might lead to Out Of Memory.
As a basic suggestion from the article, you should avoid using ImageSource for your src property. You can also use the loadMode with async value to prevent freezing of your main UI when your images are downloaded.
Thanks @NickIliev I ran across that article but am still at a road block because I don't know of a way to load an image file from disk without using imageSource when the image isn't embedded into the app directory. Is there a way to do this? This link offers two options both of which it seems like the images come from within the app directory which I can't write to at runtime - is this a correct statement?:
Even if using imagesource causes the image to get stored internally in java are there any ideas why calling both GC() and java.lang.System.gc() wouldn't cause the image to be cleared from the java side?
Indeed you can load an image stored on the user device (but outside your app) only with methods from ImageSouce module. As a rule of thumb, I would suggest rethinking this workflow if possible - working with a lot of large images would cause OOM as the JavaScript GC is not frequently called as the Java one. |Still you can try optimization techniques like setting gcThrottleTime parameter or memoryCheckInterval and freeMemoryRatio parameters.
The thing is that you are loading large objects via loop (iterating list-view items) and both are bad practices when talking about optimizations. In the same time, the large images would take space on the user's device which is also a downside.
Even if you worry that the users will have to download the images, again and again, consider the following:
The Image module will use internal memory and disk cache, so when loaded the module stores the images in the memory cache, and when they are not needed anymore, the Image module saves the images in the disk cache. This way the next time the application needs the same image NativeScript will load it from memory or the disk cache.
So using src with URL string gives you almost the same experience but the images will be stored in a cache (Android) and reused on next application start. In the same time, you won't have to worry about GCs and OOM.
Just wanted to circle around and say that the oom issues involving loading images from disk on android were solved by using nativescript-fresco.
so I should be able to see if the java.gc will fix it.
@NathanaelA did you get the chance to check it out?
Closing due to lack of response, we'll reopen if there's a need.
Most helpful comment
Just wanted to circle around and say that the oom issues involving loading images from disk on android were solved by using nativescript-fresco.