Módulo GC
JARU libera la memoria por ti. El recolector de basura (GC, garbage collector) busca los objetos que tu programa ya no puede usar (cadenas, listas, mapas, instancias, imágenes…) y los borra. En JARU no se puede liberar memoria a mano: de eso se encarga siempre el recolector.
El módulo GC sirve para decidir cuándo trabaja: pedir una pasada en un momento tranquilo, apagarlo durante una parte delicada o cambiar cada cuánto pasa.
use GC
Cómo trabaja
- Marca y barrido (mark and sweep): marca todos los objetos que tu programa todavía puede alcanzar y borra los demás.
- No corre en segundo plano. Salta dentro de tu programa, cuando este reserva memoria y la que usa pasa de un umbral. Mientras trabaja, tu programa espera.
- El umbral sale del factor. Después de cada pasada, la siguiente llega cuando la memoria en uso es la que quedó por el factor. Con el 1,7 de Windows, cuando crece un 70 %; con el 1,2 de las placas, un 20 %.
- Está encendido desde que arranca el programa.
- Si una reserva de memoria falla, la VM pasa el recolector (si está encendido) y lo intenta hasta tres veces. Si sigue sin haber memoria, la VM se cierra: no hay error que capturar.
Plataformas
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
GC.factor() de serie | 1,7 | 1,2 | 1,2 | 1,2 |
memFree() | 0 | La RAM interna libre, sin la PSRAM | Toda la memoria libre, PSRAM incluida | Toda la memoria libre, PSRAM incluida |
En las placas, el factor es más bajo para que la memoria crezca menos entre pasadas.
Funciones
| Función | Qué hace | Devuelve |
|---|---|---|
collect() | Pasa el recolector ahora. | Los bytes que ha liberado |
isEnable() | Dice si está encendido. | true o false |
enable() | Lo enciende. | nil |
disable() | Lo apaga. | nil |
factor() | Lee o cambia el factor. | El factor, o true al cambiarlo |
Sus errores se lanzan, así que se capturan con try: con un número de argumentos que no es el suyo, ERR_INVALID_ARG_NUMBER.
collect
collect() pasa el recolector ahora y devuelve los bytes que ha liberado, en un entero. Pasa aunque esté apagado, y lo deja como estaba.
use GC
for (var i = 0; i < 1000; i++)
var temporal = "Cadena temporal " + i.toString()
end
var liberados = GC.collect()
println("Ha liberado memoria: ", liberados > 0) // Ha liberado memoria: true
La cuenta es la de los objetos de JARU: los píxeles de las imágenes no entran (sí en memUse()).
Llámala en un momento en que una pausa no se note: al cambiar de nivel, en un menú, antes de una parte que tiene que ir fluida.
isEnable
isEnable() devuelve true si el recolector está encendido, y false si está apagado.
use GC
if (GC.isEnable()) then
println("El GC está encendido") // El GC está encendido
end
enable
enable() vuelve a encender el recolector: desde ese momento, salta solo otra vez cuando hace falta. Devuelve nil.
disable
disable() apaga el recolector: la memoria que ya no se usa no se libera hasta que llamas a collect() o lo vuelves a encender. Devuelve nil.
use GC
GC.disable()
// Aquí no hay pasadas: ninguna pausa del recolector
GC.collect() // la pasada, cuando conviene
GC.enable()
Con el recolector apagado, la memoria solo crece. Si se acaba, la VM no puede liberar nada y se cierra. Apágalo solo durante una parte corta y vuelve a encenderlo.
factor
factor() devuelve el factor de crecimiento, con decimales. factor(n) lo cambia y devuelve true; n es un entero o un decimal, y con otro tipo lanza ERR_TYPE_MISMATCH.
use GC
println(GC.factor()) // 1.7000000476837158 en Windows (1,2 en las placas)
GC.factor(2) // la siguiente pasada, cuando la memoria se doble
- Más alto: menos pasadas, y más memoria en uso entre una y otra.
- Más bajo: más pasadas, y menos memoria.
- Con 1 o menos, el recolector pasa en casi cada reserva de memoria y el programa va mucho más lento. No comprueba el valor: no lo bajes de 1.
Se guarda con la precisión de un decimal corto: el 1,7 de serie se lee como 1.7000000476837158.
Cuánta memoria usa tu programa
Dos funciones de la librería estándar te dicen cómo va la memoria:
memUse(): los bytes que tiene reservados tu programa, con los píxeles de las imágenes.memFree(): la memoria libre de la placa (ver Plataformas). En Windows devuelve 0.
println("En uso: ", memUse(), " bytes")
println("Libre: ", memFree(), " bytes") // en Windows, Libre: 0 bytes
Ejemplo completo
Una parte que crea mucha basura, sin pasadas del recolector en medio, y una pasada al terminar.
use GC
println("GC encendido: ", GC.isEnable())
// Mientras se crea la basura, ninguna pausa
GC.disable()
for (var i = 0; i < 1000; i++)
var temporal = "Cadena temporal " + i.toString()
end
println("Encendido durante la parte: ", GC.isEnable())
// La pasada, al terminar
var liberados = GC.collect()
println("Ha liberado memoria: ", liberados > 0)
GC.enable()
// En una placa con poca memoria, pasadas algo más frecuentes
GC.factor(1.1)
println("Factor: ", GC.factor())
Lo que escribe:
GC encendido: true
Encendido durante la parte: false
Ha liberado memoria: true
Factor: 1.100000023841858
Trampas y problemas conocidos
- No hay forma de liberar un objeto a mano. Si quieres que una lista grande se libere, deja de usarla (por ejemplo,
lista = nil) y el recolector la borrará en su próxima pasada. - El recolector para tu programa mientras trabaja. En un juego puede notarse como un tirón: pide la pasada con
collect()en un momento tranquilo. - Apagado, la memoria solo crece, y si se acaba la VM se cierra.
- Un factor de 1 o menos hace el programa mucho más lento.
collect()no cuenta los píxeles de las imágenes en los bytes que devuelve.