Garbage Collection
QuickJS uses automatic garbage collection with configurable thresholds. These functions allow you to control and trigger garbage collection.
JS_RunGC
Manually triggers garbage collection.
void JS_RunGC(JSRuntime *rt);
Parameters
The runtime to run garbage collection on.
Description
Immediately runs a garbage collection cycle on the runtime. This will:
- Mark all reachable objects
- Free all unreachable objects
- Call finalizers for freed objects
- Reclaim memory
Garbage collection normally runs automatically when memory allocation crosses the GC threshold. Manual triggering is useful for:
- Freeing memory at strategic points (e.g., between requests)
- Testing and debugging memory leaks
- Ensuring cleanup before measuring memory usage
- Controlling GC timing in performance-critical code
Garbage collection is already automatic. You typically don’t need to call this function unless you have specific timing or memory requirements.
Example
#include <quickjs.h>
int main() {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
// Run some JavaScript that creates temporary objects
JS_Eval(ctx, "for (let i = 0; i < 1000; i++) { let x = {data: new Array(100)}; }",
62, "<input>", JS_EVAL_TYPE_GLOBAL);
// Manually trigger GC to free temporary objects
JS_RunGC(rt);
// Check memory usage after GC
JSMemoryUsage stats;
JS_ComputeMemoryUsage(rt, &stats);
printf("Memory after GC: %lld bytes\n", (long long)stats.malloc_size);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
Example: Periodic GC
// Run GC periodically in a server application
#include <quickjs.h>
#include <time.h>
typedef struct {
JSRuntime *rt;
time_t last_gc;
int requests_since_gc;
} ServerContext;
void handle_request(ServerContext *server, const char *script) {
JSContext *ctx = JS_NewContext(server->rt);
// Execute request
JS_Eval(ctx, script, strlen(script), "request", JS_EVAL_TYPE_GLOBAL);
JS_FreeContext(ctx);
server->requests_since_gc++;
// Run GC every 100 requests or every 60 seconds
time_t now = time(NULL);
if (server->requests_since_gc >= 100 ||
(now - server->last_gc) >= 60) {
JS_RunGC(server->rt);
server->last_gc = now;
server->requests_since_gc = 0;
}
}
Example: Debug Memory Leaks
// Use GC to detect memory leaks
void check_for_leaks(JSRuntime *rt) {
// Enable leak detection
JS_SetDumpFlags(rt, JS_DUMP_LEAKS | JS_DUMP_GC);
// Run GC to collect unreferenced objects
JS_RunGC(rt);
// GC will print leaked objects to stdout
}
JS_SetGCThreshold
Sets the garbage collection threshold.
void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold);
Parameters
The runtime to configure.
Number of bytes to allocate before triggering automatic garbage collection.
Description
Controls how frequently automatic garbage collection runs. When total allocated memory increases by gc_threshold bytes since the last GC, a new GC cycle is triggered.
Lower threshold:
- More frequent GC cycles
- Lower peak memory usage
- Higher CPU overhead
- More predictable memory usage
Higher threshold:
- Less frequent GC cycles
- Higher peak memory usage
- Lower CPU overhead
- Better performance for allocation-heavy code
The default GC threshold is automatically set based on runtime characteristics. You typically only need to adjust this for performance tuning or memory-constrained environments.
Example
#include <quickjs.h>
int main() {
JSRuntime *rt = JS_NewRuntime();
// Set GC to run every 256 KB of allocations
JS_SetGCThreshold(rt, 256 * 1024);
JSContext *ctx = JS_NewContext(rt);
// This will trigger multiple GC cycles
const char *code =
"let arrays = [];\n"
"for (let i = 0; i < 100; i++) {\n"
" arrays.push(new Array(10000));\n" // Allocates memory
"}";
JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
Example: Memory-Constrained Device
// Configure for embedded system with limited memory
JSRuntime *create_embedded_runtime(void) {
JSRuntime *rt = JS_NewRuntime();
// Set low memory limit
JS_SetMemoryLimit(rt, 2 * 1024 * 1024); // 2 MB max
// Set aggressive GC threshold (run GC every 128 KB)
JS_SetGCThreshold(rt, 128 * 1024);
return rt;
}
// Configure for performance-critical application
JSRuntime *create_performance_runtime(void) {
JSRuntime *rt = JS_NewRuntime();
// Set high memory limit
JS_SetMemoryLimit(rt, 512 * 1024 * 1024); // 512 MB max
// Set relaxed GC threshold (run GC every 10 MB)
JS_SetGCThreshold(rt, 10 * 1024 * 1024);
return rt;
}
JS_GetGCThreshold
Retrieves the current garbage collection threshold.
size_t JS_GetGCThreshold(JSRuntime *rt);
Parameters
Returns
The current GC threshold in bytes.
Description
Returns the number of bytes that must be allocated before automatic garbage collection triggers.
Example
#include <quickjs.h>
void print_gc_config(JSRuntime *rt) {
size_t threshold = JS_GetGCThreshold(rt);
printf("GC threshold: %zu bytes (%.2f MB)\n",
threshold, threshold / 1024.0 / 1024.0);
}
int main() {
JSRuntime *rt = JS_NewRuntime();
// Check default threshold
print_gc_config(rt);
// Modify threshold
JS_SetGCThreshold(rt, 512 * 1024);
// Verify new threshold
print_gc_config(rt);
JS_FreeRuntime(rt);
return 0;
}
Example: Adaptive GC
// Adjust GC threshold based on memory pressure
void adjust_gc_threshold(JSRuntime *rt) {
JSMemoryUsage stats;
JS_ComputeMemoryUsage(rt, &stats);
size_t current_threshold = JS_GetGCThreshold(rt);
// If we're using > 80% of our memory limit, make GC more aggressive
if (stats.malloc_size > stats.malloc_limit * 0.8) {
size_t new_threshold = current_threshold / 2;
JS_SetGCThreshold(rt, new_threshold);
printf("Memory pressure high, reducing GC threshold to %zu\n",
new_threshold);
}
// If we're using < 50% of our limit, relax GC frequency
else if (stats.malloc_size < stats.malloc_limit * 0.5) {
size_t new_threshold = current_threshold * 2;
JS_SetGCThreshold(rt, new_threshold);
printf("Memory pressure low, increasing GC threshold to %zu\n",
new_threshold);
}
}
Example: GC Statistics
// Track GC behavior over time
typedef struct {
size_t gc_count;
size_t total_freed;
double avg_gc_time;
} GCStats;
void log_gc_stats(JSRuntime *rt, GCStats *stats) {
printf("\nGC Statistics:\n");
printf(" Threshold: %zu bytes\n", JS_GetGCThreshold(rt));
printf(" Total GCs: %zu\n", stats->gc_count);
printf(" Avg GC time: %.2f ms\n", stats->avg_gc_time);
printf(" Total freed: %zu bytes\n", stats->total_freed);
}
Best Practices
When to Use Manual GC
// Good: Between independent tasks
void process_batch(JSRuntime *rt, const char **scripts, int count) {
for (int i = 0; i < count; i++) {
JSContext *ctx = JS_NewContext(rt);
JS_Eval(ctx, scripts[i], strlen(scripts[i]), "script",
JS_EVAL_TYPE_GLOBAL);
JS_FreeContext(ctx);
}
// Clean up after batch
JS_RunGC(rt);
}
// Bad: Too frequent manual GC
void process_items(JSRuntime *rt, int count) {
for (int i = 0; i < count; i++) {
// Process item...
JS_RunGC(rt); // Don't do this! Too frequent.
}
}
Memory-Constrained Configuration
// Complete setup for embedded/constrained environment
JSRuntime *create_constrained_runtime(void) {
JSRuntime *rt = JS_NewRuntime();
// Strict memory limit
JS_SetMemoryLimit(rt, 4 * 1024 * 1024); // 4 MB
// Aggressive GC
JS_SetGCThreshold(rt, 256 * 1024); // 256 KB
// Enable GC debugging
JS_SetDumpFlags(rt, JS_DUMP_GC | JS_DUMP_GC_FREE);
return rt;
}
Excessive manual GC calls can hurt performance. Let the automatic GC do its job unless you have specific requirements. Only call JS_RunGC() at strategic points like between requests or after freeing large data structures.