Profiling Vector Memory Leaks in Matter.js
High-frequency physics simulations built with Matter.js often suffer
from frame drops and memory bloat caused by excessive allocations of
vector objects within the update loop. This article outlines how to
identify and profile Matter.Vector allocation spikes using
browser developer tools, trace retaining paths, and refactor physics
code to maintain a flat memory footprint.
Why Matter.js Vectors Leak
Matter.js relies heavily on 2D vectors for positions, velocities, and
forces. Methods such as Vector.create(x, y),
Vector.add(), and Vector.sub() return fresh
object instances:
// Creates a new object reference every execution
const force = Matter.Vector.create(0, 0.05);
Matter.Body.applyForce(body, body.position, force);When called repeatedly inside beforeUpdate events or
requestAnimationFrame cycles, thousands of short-lived
{ x, y } objects are allocated every second. If references
to these vectors are inadvertently retained in arrays, custom cache
structures, or closures, the garbage collector cannot free them, causing
a steady climb in heap size. Even without permanent retention, frequent
allocations trigger garbage collection (GC) thrashing, causing visible
micro-stutters.
Step-by-Step Profiling with Chrome DevTools
To isolate transient or leaking vector objects, use the Chrome DevTools Memory profiler.
1. Capture Allocation Instrumentation on Timeline
- Open your application in Google Chrome.
- Open DevTools (
F12orCtrl+Shift+I/Cmd+Option+I) and switch to the Memory panel. - Select Allocation instrumentation on timeline.
- Click Start to begin profiling while your Matter.js physics engine is actively stepping.
- Let the simulation run for 10–20 seconds to establish a baseline, then perform the actions suspected of causing leaks.
- Click the red record button to stop.
2. Isolate Vector Allocations
The timeline displays vertical bars representing memory allocations:
- Blue bars indicate memory that is still retained at the end of the recording.
- Grey bars indicate memory allocated and subsequently reclaimed by garbage collection.
A sustained staircase pattern of blue bars confirms a genuine memory leak. To inspect vectors:
- Drag a selection window over a section of the timeline with high blue spikes.
- In the Constructor view, filter by
Object. - Expand
Objectand sort by Shallow Size or Distance. Matter.js vectors appear as generic objects containing onlyxandynumeric properties. - If you have wrapped vector creation in custom classes, search directly for your class name.
3. Trace Retaining Paths
Click on an individual vector instance to populate the Retainers panel at the bottom:
- Look for the chain of references keeping the object alive.
- Identify whether the vector is held by a custom array (such as an
unpruned history of past body positions), an internal Matter.js property
(such as
body.partsor custom plugin state), or an active closure context (such as an event listener callback created inside a loop). - Check the Distance column: lower numbers indicate
objects closer to the root (e.g.,
window), which are actively preventing garbage collection.
Taking Comparative Heap Snapshots
For persistent leaks:
- In the Memory panel, select Heap snapshot.
- Take Snapshot 1 immediately after the physics world stabilizes.
- Allow the simulation to run for several minutes.
- Take Snapshot 2.
- Switch the view dropdown from Summary to Comparison, comparing Snapshot 2 against Snapshot 1.
- Sort by # Delta. If
Objectshows a large positive delta matching your frame count multiplied by your bodies, vectors are failing to clean up.
Eliminating Vector Allocations
Once you find the offending code, eliminate the creation of new vector instances.
Mutate Existing Vectors
Modify the properties of an existing vector instead of generating a new one:
// Avoid
const displacement = Matter.Vector.sub(bodyA.position, bodyB.position);
// Preferred: Reuse a dedicated vector object
displacement.x = bodyA.position.x - bodyB.position.x;
displacement.y = bodyA.position.y - bodyB.position.y;Pre-allocate Static Vectors
For static values like gravity offsets or standard directions, allocate them outside the execution loop:
// Defined once in module or setup scope
const ZERO_FORCE = Object.freeze({ x: 0, y: 0 });
const UPWARD_FORCE = { x: 0, y: -0.05 };
Matter.Events.on(engine, 'beforeUpdate', () => {
Matter.Body.applyForce(playerBody, playerBody.position, UPWARD_FORCE);
});Implement a Vector Pool
For complex math requiring intermediary calculations, use an object pool:
const VectorPool = {
pool: [],
acquire(x = 0, y = 0) {
const v = this.pool.pop() || { x: 0, y: 0 };
v.x = x;
v.y = y;
return v;
},
release(v) {
this.pool.push(v);
}
};
// Usage inside the physics loop
const tempVec = VectorPool.acquire(target.x, target.y);
// ... compute physics ...
VectorPool.release(tempVec);After implementing these modifications, run another Allocation instrumentation session to verify that vector allocations drop to zero during steady-state simulation.