Hi,
I’m hoping that someone might be able to give me some guidance/advice on a garbage collection issue I’m having.
I’ve been using Spidermonkey in my embedding for many years, but have now had an issue reported to me where I am running out of memory. In my embedding this seems to happen when a user is creating an object which contains ~900,000 properties.
I’m struggling to understand why there isn’t an attempt to reclaim memory by doing garbage collection before the out of memory error occurs though.
For background, I declare a runtime/context with 50MB of memory. I then use JS_SetGCCallback to register a GC callback function. In this I use JS_GetGCParameter(cx, JSGC_MAX_BYTES) and JS_GetGCParameter(cx, JSGC_BYTES) to see what percentage of the garbage collection memory is being used at the beginning and end of garbage collection.
If at the end of garbage collection the GC memory usage is > 90%, or alternatively if at the start of garbage collection the usage is > 70% and at the end the usage has not dropped my more than 1%, then I double the GC memory by calling
JS_SetGCParameter(cx, JSGC_MAX_BYTES, new_size);
I’ve added some debugging to see what is going on and in a release build I see the following, just before the OOM error
Part 100220 (465/2791)
Called dj_GC_callback_func at 13:28:13.550 with status BEGIN reason 6
Max bytes=52428800, bytes=49512448 (94%)
Called dj_GC_callback_func at 13:28:13.573 with status END reason 6
Max bytes=52428800, bytes=32133120 (61%)
900888 elements
Component 326
Called dj_GC_callback_func at 13:28:13.593 with status BEGIN reason 1
Max bytes=52428800, bytes=40996864 (78%)
Called dj_GC_callback_func at 13:28:13.612 with status END reason 1
Max bytes=52428800, bytes=41005056 (78%)
GC memory use of 41005056 > 70% of 52428800 and not dropped
Increasing GC size to 104857600
Called dj_GC_callback_func at 13:28:13.994 with status BEGIN reason 4
Max bytes=104857600, bytes=104857600 (100%)
Called dj_GC_callback_func at 13:28:14.076 with status END reason 4
Max bytes=104857600, bytes=53841920 (51%)
Component 325
Called dj_GC_callback_func at 13:28:14.343 with status BEGIN reason 1
Max bytes=104857600, bytes=84996096 (81%)
Called dj_GC_callback_func at 13:28:14.377 with status END reason 1
Max bytes=104857600, bytes=45277184 (43%)
Component 7
OOM at 13:28:14.798 Max bytes=104857600, bytes=104857600)
From this I can see that by GC callback function is called, firstly with reason ‘ALLOC_TRIGGER’ (6) and the GC memory drops from 94% to 61%.
Then shortly after it is called again with reason ‘EAGER_ALLOC_TRIGGER’ (1) and the GC memory doesn’t drop so I expand the memory to 104857600 bytes.
Then it is called with reason ‘LAST_DITCH’ (4) because the GC memory gets to 100% but it then successfully drops to 51%.
It is then called again with EAGER_ALLOC_TRIGGER and manages to reclaim memory again (81% drops to 43%).
Then suddenly ~500ms later I get an OOM error.
Why doesn’t SM try to do garbage collection again before failing with the error?
I’ve tried with a debug build and it gets further, but fails in a similar issue creating an object with ~900,000 properties again. Interestingly though, in this case I do not get the ‘LAST_DITCH’ call.
Called dj_GC_callback_func at 13:55:52.078 with status BEGIN reason 1
Max bytes=104857600, bytes=40353792 (38%)
Called dj_GC_callback_func at 13:55:52.655 with status END reason 1
Max bytes=104857600, bytes=24379392 (23%)
Component 7
Component 9
Part 100432 (523/2791)
Called dj_GC_callback_func at 13:56: 2.025 with status BEGIN reason 6
Max bytes=104857600, bytes=42950656 (40%)
Called dj_GC_callback_func at 13:56: 4.712 with status END reason 6
Max bytes=104857600, bytes=40923136 (39%)
900888 elements
Component 326
OOM at 13:56:27.553 Max bytes=104857600, bytes=104857600)
I’m presuming that as my embedding is trying to make an object with so many properties, this is a very different use case to the normal usage in Firefox and so for some reason I don’t understand, in my case, garbage collection is not being triggered. Perhaps it is somehow because I am making so many objects/properties and collecting them over a small time duration (in ms)? Maybe it is too soon after GC was previously done or something? However I’m completely guessing and this could be garbage.
I have looked in GCAPI.h and I can see all sorts of parameters that can be changed in JSGCParamKey for altering the garbage collector. However, I’m at a bit of a loss to know what values I should be trying to change and what to change them to.
Garbage collection has always been a bit of a ‘black box’ for me. It just ‘worked’. However, it now seems I have ‘Pandora’s box’… ![]()
Can anyone provide me with any guidance/advice on why garbage collection is not being triggered for me, causing the out of memory error?
Can anyone recommend which JSGCParamKey values I should be trying to change, or if there is something else that I’m missing because I’m being stupid…
I would be very grateful for any thoughts anyone might have.
Many thanks in advance.
Miles