apache/flink · error · IllegalStateException

MemorySegment can be freed only once!

Error message

MemorySegment can be freed only once!

What it means

MemorySegment.free() releases a segment exactly once, guarded by an AtomicBoolean. On a second free() the call is normally a no-op unless the static flag checkMultipleFree (set via MemorySegment.setCheckMultipleFree, primarily used in tests) is enabled, in which case it throws IllegalStateException('MemorySegment can be freed only once!'). free() also invalidates the segment by setting address = addressLimit + 1 so any later data access fails.

Source

Thrown at flink-core/src/main/java/org/apache/flink/core/memory/MemorySegment.java:244

     * @return <tt>true</tt>, if the memory segment has been freed, <tt>false</tt> otherwise.
     */
    @VisibleForTesting
    public boolean isFreed() {
        return address > addressLimit;
    }

    /**
     * Frees this memory segment.
     *
     * <p>After this operation has been called, no further operations are possible on the memory
     * segment and will fail. The actual memory (heap or off-heap) will only be released after this
     * memory segment object has become garbage collected.
     */
    public void free() {
        if (isFreedAtomic.getAndSet(true)) {
            // the segment has already been freed
            if (checkMultipleFree) {
                throw new IllegalStateException("MemorySegment can be freed only once!");
            }
        } else {
            // this ensures we can place no more data and trigger
            // the checks for the freed segment
            address = addressLimit + 1;
            offHeapBuffer = null; // to enable GC of unsafe memory
            if (cleaner != null) {
                cleaner.run();
                cleaner = null;
            }
        }
    }

    /**
     * Checks whether this memory segment is backed by off-heap memory.
     *
     * @return <tt>true</tt>, if the memory segment is backed by off-heap memory, <tt>false</tt> if
     *     it is backed by heap memory.

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Establish a single owner responsible for freeing/recycling each segment and audit error paths so cleanup never frees a segment the failure path already freed.
  2. Guard every free site with if (!segment.isFreed()) { segment.free(); }.
  3. Trace which component freed first (log isFreed()/owner via segment.getOwner()) and remove the duplicate free call.
  4. Only in tests where a double free is known-benign, wrap with MemorySegment.setCheckMultipleFree(false) — never as a production fix.

Example fix

// before
segment.free(); // second call throws when multiple-free checks are on

// after
if (!segment.isFreed()) {
    segment.free();
}
Defensive patterns

Strategy: validation

Validate before calling

if (!segment.isFreed()) {
    segment.free();
}

Try / catch

try {
    segment.free();
} catch (IllegalStateException e) {
    // only reachable when checkMultipleFree is enabled; fix the duplicate free site, do not swallow
    throw e;
}

Prevention

When it happens

Trigger: Calling free() (or a recycling path that frees) twice on the same MemorySegment: e.g. an owner frees the segment on an error path and cleanup code frees it again, or the same segment is returned to a MemoryPool/network-buffer pool twice.

Common situations: Double-recycle bugs in network buffer pools (buffer recycled once on emit and once on failure); memory-manager returnSegment paths executed twice; shared ownership of a segment across components without a single designated owner; unit tests that enable checkMultipleFree surfacing pre-existing double frees.

Related errors


AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14). Data as JSON: /api/errors/ac53e6211c1fd20b. Report an issue: GitHub.