GC module
JARU frees memory for you. The garbage collector (GC) looks for the objects your program can no longer use (strings, lists, maps, instances, images…) and deletes them. In JARU you cannot free memory by hand: the collector always takes care of it.
The GC module is for deciding when it works: asking for a pass at a quiet moment, turning it off during a delicate part or changing how often it runs.
use GC
How it works
- Mark and sweep: it marks every object your program can still reach and deletes the rest.
- It does not run in the background. It kicks in inside your program, when your program reserves memory and the memory in use goes over a threshold. While it works, your program waits.
- The threshold comes from the factor. After each pass, the next one comes when the memory in use is what was left times the factor. With Windows' 1.7, when it grows by 70 %; with the boards' 1.2, by 20 %.
- It is on from the moment the program starts.
- If a memory reservation fails, the VM runs the collector (if it is on) and tries up to three times. If there is still no memory, the VM closes: there is no error to catch.
Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
Default GC.factor() | 1.7 | 1.2 | 1.2 | 1.2 |
memFree() | 0 | The free internal RAM, without the PSRAM | All the free memory, PSRAM included | All the free memory, PSRAM included |
On the boards, the factor is lower so that memory grows less between passes.
Functions
| Function | What it does | Returns |
|---|---|---|
collect() | Runs the collector now. | The bytes it has freed |
isEnable() | Tells whether it is on. | true or false |
enable() | Turns it on. | nil |
disable() | Turns it off. | nil |
factor() | Reads or changes the factor. | The factor, or true when changing it |
Their errors are thrown, so try catches them: with the wrong number of arguments, ERR_INVALID_ARG_NUMBER.
collect
collect() runs the collector now and returns the bytes it has freed, as an integer. It runs even if the collector is off, and leaves it as it was.
use GC
for (var i = 0; i < 1000; i++)
var temporary = "Temporary string " + i.toString()
end
var freed = GC.collect()
println("It freed memory: ", freed > 0) // It freed memory: true
The count is that of the JARU objects: the pixels of the images are not included (they are in memUse()).
Call it at a moment when a pause does not show: when changing level, in a menu, before a part that has to run smoothly.
isEnable
isEnable() returns true if the collector is on, and false if it is off.
use GC
if (GC.isEnable()) then
println("The GC is on") // The GC is on
end
enable
enable() turns the collector back on: from then on, it kicks in by itself again when needed. It returns nil.
disable
disable() turns the collector off: the memory no longer in use is not freed until you call collect() or turn it back on. It returns nil.
use GC
GC.disable()
// No passes here: no collector pauses
GC.collect() // the pass, when it suits you
GC.enable()
With the collector off, memory only grows. If it runs out, the VM cannot free anything and closes. Turn it off only during a short part and turn it back on.
factor
factor() returns the growth factor, with decimals. factor(n) changes it and returns true; n is an integer or a decimal, and with another type it throws ERR_TYPE_MISMATCH.
use GC
println(GC.factor()) // 1.7000000476837158 on Windows (1.2 on the boards)
GC.factor(2) // the next pass, when memory doubles
- Higher: fewer passes, and more memory in use between one and the next.
- Lower: more passes, and less memory.
- With 1 or less, the collector runs at almost every memory reservation and the program becomes much slower. It does not check the value: do not set it below 1.
It is stored with the precision of a short decimal: the default 1.7 reads as 1.7000000476837158.
How much memory your program uses
Two functions of the standard library tell you how memory is going:
memUse(): the bytes your program has reserved, including the pixels of the images.memFree(): the free memory of the board (see Platforms). On Windows it returns 0.
println("In use: ", memUse(), " bytes")
println("Free: ", memFree(), " bytes") // on Windows, Free: 0 bytes
Full example
A part that creates a lot of garbage, with no collector passes in between, and one pass at the end.
use GC
println("GC on: ", GC.isEnable())
// While the garbage is created, no pauses
GC.disable()
for (var i = 0; i < 1000; i++)
var temporary = "Temporary string " + i.toString()
end
println("On during the part: ", GC.isEnable())
// The pass, at the end
var freed = GC.collect()
println("It freed memory: ", freed > 0)
GC.enable()
// On a board with little memory, somewhat more frequent passes
GC.factor(1.1)
println("Factor: ", GC.factor())
What it prints:
GC on: true
On during the part: false
It freed memory: true
Factor: 1.100000023841858
Pitfalls and known issues
- There is no way to free an object by hand. If you want a large list to be freed, stop using it (for example,
list = nil) and the collector will delete it in its next pass. - The collector stops your program while it works. In a game it can show as a hitch: ask for the pass with
collect()at a quiet moment. - Off, memory only grows, and if it runs out the VM closes.
- A factor of 1 or less makes the program much slower.
collect()does not count the pixels of the images in the bytes it returns.