Skip to content
ekmettPublic

Repository files navigation

jam

build docs coverage docker nix runtime build issues commits

CMake: 4.4+ Ninja: 1.12+ C++: 26 Clang: 23 Java: 25 GraalVM: 25.3.4.1 SubstrateVM: 25.3.4.1

OS: Linux · macOS · Windows CPU: x86-64 · ARM64

license: BSD-2-Clause OR Apache-2.0 Contributor Covenant: 2.0

docs: read coverage percentage Nix: flake Docker: GHCR dist: downloads

jam is a compacting generational garbage collector for C++26. Build lists, trees or graphs, keep roots, and let the collector reclaim what you can no longer reach. Sharing and cycles are fine. A pointer takes four bytes, and objects need no GC header or intrusive base class.

Tell us where your pointers live and we generate the walk:

#include <cassert>
#include <cstdint>
#include <new>
import jam.unqualified;

struct node {
  ptr<node> next;
  std::uint64_t data;
  static constexpr auto manifest = make_manifest<node>(&node::next);
};

int main() {
  heap heap{{.workers = 4}};
  heap_scope scope{heap};

  auto const a = mk<node>(nullptr, 42u);
  auto const b = mk<node>(a, 99u);
  a->next = b;
  root answer = a;

  collect();

  assert(answer->data == 42);
  assert(answer->next->next == answer.get());
}

Here next is a managed edge; data is just data. The two nodes form a cycle. answer keeps it alive, and collection repairs the links as objects move. Drop the root and the whole cycle becomes collectible.

Manifests compose through embedded records and arrays. You can store SIMD pointer vectors too, or supply a trace hook when the layout depends on a tag. The tracing guide and pointer vector examples pick up from there. A weak_ptr<T> follows movement without keeping its target alive. A weak_root<T> does the same from outside the heap and stays valid across collection. Weak associations and finalizers let a live key keep a value alive and schedule a managed cleanup action when the key dies.

There are a few things you have to get right:

  • List every managed edge in a manifest or trace hook. Unlisted fields are data; Jam cannot discover a pointer you haven't told it about.
  • Keep roots outside the heap and pointers inside it. The local a and b above are stale after collect(); get fresh pointers from answer. Raw pointers and references into the heap can also expire when allocation grows it.
  • Use the owning heap_scope when working with pointers or roots, including when destroying roots. Mixing heaps is undefined behavior. Collection can use several workers, but you must stop mutating the heap before collecting.
  • Objects move as bytes. Jam does not call their move constructors or run their destructors when reclaiming them. Self-links through ptr work; raw self-pointers don't. Cleanup needs an explicit finalizer action, not a C++ destructor. See the storage contract before choosing what to put in the heap.

You choose when to call collect(). Most calls collect only young objects; periodically one collects both generations. Writes from old objects to young ones are tracked automatically. Collection policy and sizing cover the controls when you need them.

To try it, you'll need Clang 23+, CMake 4.4+ and Ninja on macOS, Linux or Windows. The build guide has the commands. import jam.unqualified; puts the names in scope as above; use import jam; for qualified names.

The topic guides go deeper; the API reference documents the individual operations.

Jam also backs HotSpot, GraalVM and Native Image. The runtime integration lives here too; the default build is still just the C++ library.

License and contact

A smiling sanitation worker with a broom

See LICENSE.md for the dual BSD-2-Clause/Apache-2.0 license and individual source notices for retained upstream terms.

Contributions and bug reports are welcome through GitHub. Edward Kmett can also be reached as ekmett on Libera Chat and @kmett on Twitter/X.