1:MarkSweepCollector.java
package memory;

import java.util.*;

/**
 * Traces explicit roots and sweeps a supplied object map; it is not a JVM
 * garbage collector.
 */
public final class MarkSweepCollector {
    public int collect(Map<Long, HeapObject> heap, Collection<HeapObject> roots) {
        Objects.requireNonNull(heap, "heap");
        Objects.requireNonNull(roots, "roots");
        for (Map.Entry<Long, HeapObject> entry : heap.entrySet()) {
            if (entry.getKey() == null || entry.getValue() == null
                    || entry.getKey().longValue() != entry.getValue().id()) {
                throw new IllegalArgumentException("heap key must match object id");
            }
        }

        Set<Long> reachable = new LinkedHashSet<>();
        Deque<HeapObject> pending = new ArrayDeque<>();
        for (HeapObject root : roots) {
            requireMember(heap, root);
            pending.push(root);
        }
        while (!pending.isEmpty()) {
            HeapObject object = pending.pop();
            if (!reachable.add(object.id()))
                continue;
            for (HeapObject reference : object.references()) {
                requireMember(heap, reference);
                pending.push(reference);
            }
        }

        int before = heap.size();
        heap.entrySet().removeIf(entry -> !reachable.contains(entry.getKey()));
        return before - heap.size();
    }

    private static void requireMember(Map<Long, HeapObject> heap, HeapObject object) {
        Objects.requireNonNull(object, "root or reference");
        if (heap.get(object.id()) != object) {
            throw new IllegalArgumentException("object is outside the heap: " + object.id());
        }
    }

    public static void main(String[] args) {
        HeapObject root = new HeapObject(1, "Node", Set.of("next"));
        HeapObject child = new HeapObject(2, "Node", Set.of("next"));
        HeapObject garbage = new HeapObject(3, "Node", Set.of());
        root.writeField("next", child);
        Map<Long, HeapObject> heap = new LinkedHashMap<>();
        heap.put(root.id(), root);
        heap.put(child.id(), child);
        heap.put(garbage.id(), garbage);
        int removed = new MarkSweepCollector().collect(heap, Set.of(root));
        if (removed != 1 || !heap.containsKey(1L) || !heap.containsKey(2L)
                || heap.containsKey(3L))
            throw new AssertionError();
        System.out.println("MarkSweepCollector: ok");
    }
}