7

Let's say I have a long running loop:

// Let's say this loop takes 10 seconds to execute
for(let i = 0; i <= 1000000; ++i) {
    const garbage = { i };
    // some other code
}

Can the garbage collector run during the loop, or it can only run when the application is idle?

I didn't find any documentation related to this, but because Node.js has the --nouse-idle-notification which in theory disables GC, makes me think that the GC only runs when the idle notification is sent (when the main thread is not busy).

I am asking this because my loop sometimes has spikes in execution time and want to know if it's possible that the GC might run during the loop, resulting in the lag spike.

XCS
  • 27,244
  • 26
  • 101
  • 151
  • 1
    I couldn't find the source link, but I remember that if you set a variable to `null` -- garbage collection is done immediately. I found this answer when I was reading disadvantages of using `delete`. – Nishant Ghodke Oct 10 '18 at 12:56
  • 1
    @NishantGhodke I don't think that's true, it might make it available for GC, but it won't GC immediately. Also you can't use delete on variables anyway, only properties of an object. – Keith Oct 10 '18 at 13:08
  • @Keith I am sorry, by using the term "variable" I meant any reference which holds a value. – Nishant Ghodke Oct 10 '18 at 13:26

2 Answers2

20

V8 developer here. The short answer is that the GC can run at any time and will run whenever it needs to.

Note that the GC is a fairly complex system: it performs several different tasks, and does most of them in incremental steps and/or concurrently with the main thread. In particular, every allocation can trigger a bit of incremental GC work. (Which implies that by very carefully avoiding all allocations, you can construct loops that won't cause GC activity while they run; but it's never the case that loops accumulate garbage that can't get collected -- unless you have a leak in your code of course, where objects are unintentionally being kept reachable.)

Can the garbage collector run during the loop, or it can only run when the application is idle?

It absolutely can and will run during the loop.

Node.js has the --nouse-idle-notification which in theory disables GC

No, it does not. There is no way to disable GC. That flag disables one particular mechanism for triggering GC activity, but that only means that GC will be triggered by other mechanisms.

the GC only runs when the idle notification is sent (when the main thread is not busy)

No, the idea is to run some extra GC cycles when there is idle time, to save some memory when the application is not busy.

my loop sometimes has spikes in execution time and want to know if it's possible that the GC might run during the loop, resulting in the lag spike

That could be. It could possibly also have to do with optimization or deoptimization of the function. Or it could be something else -- the operating system interrupting your process or assigning it to another CPU core, for example, or hundreds of other reasons. Computers are complex machines ;-)

if you set a variable to null -- garbage collection is done immediately

No, it is not. Garbage collection is never done immediately (at least not in V8).

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Thanks for the very detailed response! I thought "no other JS can run while synchronous code is running (in the same thread)" and that synchronous code ran on the main thread couldn't be stopped by GC, but it appears that my assumption was wrong. Any suggestion on what flags to use in order to minimize the GC impact? By "reduce impact" I mean that minimizing the longest GC pause. eg: `max-old-space-size` is currently set to 3GB (out of the 4GB RAM). – XCS Oct 10 '18 at 22:19
  • "no other JS can run" true, but (1) that's by spec and not because it's technically impossible, and (2) the GC is not JS. -- The default flags are optimized for shortest possible pause times, most are less than 1ms, and even the longest pauses should be shorter than 10ms usually. Make sure the OS is not swapping memory to disk, lower `--max-old-space-size` if needed. – jmrk Oct 11 '18 at 01:47
  • Thanks, and does `--nouse-idle-notification` increase the longest GC pause or decreases it? – XCS Oct 11 '18 at 08:18
  • I don't know; I would recommend that you try it and measure. Apparently back in 2012 with Node 0.8 it was beneficial, but that was a very long time ago. If you still see a benefit from passing `--nouse-idle-notification` then I would consider that a bug in Node that deserves investigation and fixing. – jmrk Oct 12 '18 at 18:23
0

As a concept the garbage collector works in a separate thread since in this way it will not block the main thread (UI thread in most cases).

As for your example, there is no problem for the garbage collection thread running in "parallel" to this loop since the value there const garbage = {key: i} will not get removed as long as it is being referenced.

Also note that there are several generations that the garbage collector passes your values through before removing them completely.

Alon Yampolski
  • 851
  • 7
  • 15
  • But `const garbage` is no longer being referenced. The reference to each object is lost at the end of each loop iteration. – XCS Oct 10 '18 at 13:30
  • you're correct and therefore as explained in my answer, it's also not being used anymore so you don't need it and it will get pushed through the garbage collector generations until completely removed. – Alon Yampolski Oct 10 '18 at 13:38
  • This understanding of generations is not correct. When collecting the young generation, only reachable objects are promoted to the old generation. Unreachable young objects are discarded, not promoted. The theory behind it is precisely that _most_ objects die young, so scanning a relatively small young generation relatively frequently and being able to drop a bunch of objects each time saves time and memory overall. – jmrk Feb 16 '22 at 23:35