diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeAllocationScope.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java
similarity index 64%
rename from src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeAllocationScope.java
rename to src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java
index 0a69eabb61d..e29dd4f3cd0 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeAllocationScope.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java
@@ -26,42 +26,55 @@
 
 package jdk.incubator.foreign;
 
-import jdk.internal.foreign.BoundedAllocationScope;
-import jdk.internal.foreign.UnboundedAllocationScope;
+import jdk.internal.foreign.AbstractNativeScope;
 
 import java.lang.invoke.VarHandle;
 import java.util.OptionalLong;
 
 /**
- * This class provides a scope of given size, within which several allocations can be performed. An allocation scope is backed
- * by off-heap memory. Allocation scopes can be either <em>bounded</em> or <em>unbounded</em>, depending on whether the size
- * of the allocation scope is known statically. If an application knows before-hand how much memory it needs to allocate the values it needs,
- * using a <em>bounded</em> allocation scope will typically provide better performances than independently allocating the memory
- * for each value (e.g. using {@link MemorySegment#allocateNative(long)}), or using an <em>unbounded</em> allocation scope.
- * For this reason, using a bounded allocation scope is recommended in cases where programs might need to emulate native stack allocation.
+ * This class provides a scope of given size, within which several allocations can be performed. An native scope is backed
+ * by off-heap memory. Native scopes can be either <em>bounded</em> or <em>unbounded</em>, depending on whether the size
+ * of the native scope is known statically. If an application knows before-hand how much memory it needs to allocate the values it needs,
+ * using a <em>bounded</em> native scope will typically provide better performances than independently allocating the memory
+ * for each value (e.g. using {@link MemorySegment#allocateNative(long)}), or using an <em>unbounded</em> native scope.
+ * For this reason, using a bounded native scope is recommended in cases where programs might need to emulate native stack allocation.
+ * <p>
+ * Allocation scopes are thread-confined (see {@link #ownerThread()}; as such, the resulting {@code MemoryAddress} instances
+ * returned by the native scope will be backed by memory segments confined by the same owner thread as the native scope.
+ * <p>
+ * To allow for more usability, it is possible for an native scope to reclaim ownership of an existing memory segments
+ * (see {@link #register(MemorySegment)}). This might be useful to allow one or more segments which were independently
+ * created to share the same life-cycle as a given native scope - which in turns enables client to group all memory
+ * allocation and usage under a single <em>try-with-resources block</em>.
  */
-public abstract class NativeAllocationScope implements AutoCloseable {
+public abstract class NativeScope implements AutoCloseable {
 
     /**
-     * If this allocation scope is bounded, returns the size, in bytes, of this allocation scope.
-     * @return the size, in bytes, of this allocation scope (if available).
+     * If this native scope is bounded, returns the size, in bytes, of this native scope.
+     * @return the size, in bytes, of this native scope (if available).
      */
     public abstract OptionalLong byteSize();
 
     /**
-     * Returns the number of allocated bytes in this allocation scope.
-     * @return the number of allocated bytes in this allocation scope.
+     * The thread owning this native scope.
+     * @return the thread owning this native scope.
+     */
+    public abstract Thread ownerThread();
+
+    /**
+     * Returns the number of allocated bytes in this native scope.
+     * @return the number of allocated bytes in this native scope.
      */
     public abstract long allocatedBytes();
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given byte value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given byte value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
      */
@@ -73,13 +86,13 @@ public MemoryAddress allocate(MemoryLayout layout, byte value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given short value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given short value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
      */
@@ -91,13 +104,13 @@ public MemoryAddress allocate(MemoryLayout layout, short value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given int value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given int value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
      */
@@ -109,13 +122,13 @@ public MemoryAddress allocate(MemoryLayout layout, int value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given float value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given float value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
      */
@@ -127,13 +140,13 @@ public MemoryAddress allocate(MemoryLayout layout, float value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given long value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given long value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
      */
@@ -145,13 +158,13 @@ public MemoryAddress allocate(MemoryLayout layout, long value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given double value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given double value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
      */
@@ -163,13 +176,13 @@ public MemoryAddress allocate(MemoryLayout layout, double value) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout and initialize it with given address value.
+     * Allocate a block of memory in this native scope with given layout and initialize it with given address value.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover, the returned
      * address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @param value the value to be set on the newly allocated memory block.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of an address value.
      */
@@ -191,11 +204,11 @@ private static Class<?> carrierForSize(long size) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given layout. The address returned by this method is
+     * Allocate a block of memory in this native scope with given layout. The address returned by this method is
      * associated with a segment which cannot be closed. Moreover, the returned address must conform to the layout alignment constraints.
      * @param layout the layout of the block of memory to be allocated.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < layout.byteSize()}.
      */
     public MemoryAddress allocate(MemoryLayout layout) {
@@ -203,11 +216,11 @@ public MemoryAddress allocate(MemoryLayout layout) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given size. The address returned by this method is
+     * Allocate a block of memory in this native scope with given size. The address returned by this method is
      * associated with a segment which cannot be closed. Moreover, the returned address must be aligned to {@code size}.
      * @param bytesSize the size (in bytes) of the block of memory to be allocated.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < bytesSize}.
      */
     public MemoryAddress allocate(long bytesSize) {
@@ -215,38 +228,57 @@ public MemoryAddress allocate(long bytesSize) {
     }
 
     /**
-     * Allocate a block of memory in this allocation scope with given size and alignment constraint.
+     * Allocate a block of memory in this native scope with given size and alignment constraint.
      * The address returned by this method is associated with a segment which cannot be closed. Moreover,
      * the returned address must be aligned to {@code alignment}.
      * @param bytesSize the size (in bytes) of the block of memory to be allocated.
      * @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
      * @return an address which points to the newly allocated memory block.
-     * @throws OutOfMemoryError if there is not enough space left in this allocation scope, that is, if
+     * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
      * {@code limit() - size() < bytesSize}.
      */
     public abstract MemoryAddress allocate(long bytesSize, long bytesAlignment);
 
     /**
-     * Close this allocation scope; calling this method will render any address obtained through this allocation scope
-     * unusable and might release any backing memory resources associated with this allocation scope.
+     * Register a segment on this scope, which will then reclaim ownership of said segment.
+     * The input segment must be closeable - that is, it must feature the {@link MemorySegment#CLOSE} access mode.
+     * As a side-effect, the input segment will be marked as <em>not alive</em>, and a new segment will be returned.
+     * <p>
+     * The returned segment will feature only {@link MemorySegment#READ} and
+     * {@link MemorySegment#WRITE} access modes (assuming these were available in the original segment). As such
+     * the resulting segment cannot be closed directly using {@link MemorySegment#close()} - but it will be closed
+     * indirectly when this native scope is closed.
+     * @param segment the segment which will be registered on this native scope.
+     * @return a new, non closeable memory segment, backed by the same underlying region as {@code segment},
+     * but whose life-cycle is tied to that of this native scope.
+     * @throws IllegalStateException if {@code segment} is not <em>alive</em> (see {@link MemorySegment#isAlive()}).
+     * @throws NullPointerException if {@code segment == null}
+     * @throws IllegalArgumentException if {@code segment.ownerThread() != this.ownerThread()}, or if {@code segment}
+     * does not feature the {@link MemorySegment#CLOSE} access mode.
+     */
+    public abstract MemorySegment register(MemorySegment segment);
+
+    /**
+     * Close this native scope; calling this method will render any address obtained through this native scope
+     * unusable and might release any backing memory resources associated with this native scope.
      */
     @Override
     public abstract void close();
 
     /**
-     * Creates a new bounded allocation scope, backed by off-heap memory.
-     * @param size the size of the allocation scope.
-     * @return a new bounded allocation scope, with given size (in bytes).
+     * Creates a new bounded native scope, backed by off-heap memory.
+     * @param size the size of the native scope.
+     * @return a new bounded native scope, with given size (in bytes).
      */
-    public static NativeAllocationScope boundedScope(long size) {
-        return new BoundedAllocationScope(size);
+    public static NativeScope boundedScope(long size) {
+        return new AbstractNativeScope.BoundedNativeScope(size);
     }
 
     /**
-     * Creates a new unbounded allocation scope, backed by off-heap memory.
-     * @return a new unbounded allocation scope.
+     * Creates a new unbounded native scope, backed by off-heap memory.
+     * @return a new unbounded native scope.
      */
-    public static NativeAllocationScope unboundedScope() {
-        return new UnboundedAllocationScope();
+    public static NativeScope unboundedScope() {
+        return new AbstractNativeScope.UnboundedNativeScope();
     }
 }
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
index 2bfe7926e8d..8ca639cc99a 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
@@ -238,12 +238,16 @@ public MemorySegment withOwnerThread(Thread newOwner) {
         if (scope.ownerThread() == newOwner) {
             throw new IllegalArgumentException("Segment already owned by thread: " + newOwner);
         } else {
-            try {
-                return dup(0L, length, mask, scope.dup(newOwner));
-            } finally {
-                //flush read/writes to segment memory before returning the new segment
-                VarHandle.fullFence();
-            }
+            return dupAndClose(newOwner);
+        }
+    }
+
+    public MemorySegment dupAndClose(Thread newOwner) {
+        try {
+            return dup(0L, length, mask, scope.dup(newOwner));
+        } finally {
+            //flush read/writes to segment memory before returning the new segment
+            VarHandle.fullFence();
         }
     }
 
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java
new file mode 100644
index 00000000000..8b0dcf238a8
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java
@@ -0,0 +1,154 @@
+package jdk.internal.foreign;
+
+import jdk.incubator.foreign.MemoryAddress;
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.NativeScope;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.OptionalLong;
+
+public abstract class AbstractNativeScope extends NativeScope {
+
+    private final List<MemorySegment> segments = new ArrayList<>();
+    private final Thread ownerThread;
+
+    private static final int SCOPE_MASK = MemorySegment.READ | MemorySegment.WRITE; // no terminal operations allowed
+
+    AbstractNativeScope(Thread ownerThread) {
+        this.ownerThread = ownerThread;
+    }
+
+    @Override
+    public Thread ownerThread() {
+        return ownerThread;
+    }
+
+    @Override
+    public void close() {
+        for (MemorySegment segment : segments) {
+            try {
+                segment.close();
+            } catch (IllegalStateException ex) {
+                //already closed - skip
+            }
+        }
+    }
+
+    void checkOwnerThread() {
+        if (Thread.currentThread() != ownerThread()) {
+            throw new IllegalStateException("Attempt to access scope from different thread");
+        }
+    }
+
+    MemorySegment newSegment(long size, long align) {
+        MemorySegment segment = MemorySegment.allocateNative(size, align);
+        segments.add(segment);
+        return segment;
+    }
+
+    MemorySegment newSegment(long size) {
+        return newSegment(size, size);
+    }
+
+    @Override
+    public MemorySegment register(MemorySegment segment) {
+        Objects.requireNonNull(segment);
+        if (segment.ownerThread() != ownerThread()) {
+            throw new IllegalArgumentException("Cannot register segment owned by a different thread");
+        } else if (!segment.hasAccessModes(MemorySegment.CLOSE)) {
+            throw new IllegalArgumentException("Cannot register a non-closeable segment");
+        }
+        MemorySegment attachedSegment = ((AbstractMemorySegmentImpl)segment)
+                .dupAndClose(ownerThread());
+        segments.add(attachedSegment);
+        return attachedSegment
+                .withAccessModes(segment.accessModes() & SCOPE_MASK);
+    }
+
+    public static class UnboundedNativeScope extends AbstractNativeScope {
+
+        private static final long BLOCK_SIZE = 4 * 1024;
+        private static final long MAX_ALLOC_SIZE = BLOCK_SIZE / 2;
+
+        private MemorySegment segment;
+        private long sp = 0L;
+        private long size = 0L;
+
+        @Override
+        public OptionalLong byteSize() {
+            return OptionalLong.empty();
+        }
+
+        @Override
+        public long allocatedBytes() {
+            return size;
+        }
+
+        public UnboundedNativeScope() {
+            super(Thread.currentThread());
+            this.segment = newSegment(BLOCK_SIZE);
+        }
+
+        @Override
+        public MemoryAddress allocate(long bytesSize, long bytesAlignment) {
+            checkOwnerThread();
+            if (bytesSize > MAX_ALLOC_SIZE) {
+                MemorySegment segment = newSegment(bytesSize, bytesAlignment);
+                return segment.withAccessModes(SCOPE_MASK)
+                        .baseAddress();
+            }
+            for (int i = 0; i < 2; i++) {
+                long min = ((MemoryAddressImpl) segment.baseAddress()).unsafeGetOffset();
+                long start = Utils.alignUp(min + sp, bytesAlignment) - min;
+                try {
+                    MemorySegment slice = segment.asSlice(start, bytesSize)
+                            .withAccessModes(SCOPE_MASK);
+                    sp = start + bytesSize;
+                    size += Utils.alignUp(bytesSize, bytesAlignment);
+                    return slice.baseAddress();
+                } catch (IndexOutOfBoundsException ex) {
+                    sp = 0L;
+                    segment = newSegment(BLOCK_SIZE, 1L);
+                }
+            }
+            throw new AssertionError("Cannot get here!");
+        }
+    }
+
+    public static class BoundedNativeScope extends AbstractNativeScope {
+        private final MemorySegment segment;
+        private long sp = 0L;
+
+        @Override
+        public OptionalLong byteSize() {
+            return OptionalLong.of(segment.byteSize());
+        }
+
+        @Override
+        public long allocatedBytes() {
+            return sp;
+        }
+
+        public BoundedNativeScope(long size) {
+            super(Thread.currentThread());
+            this.segment = newSegment(size, 1);
+        }
+
+        @Override
+        public MemoryAddress allocate(long bytesSize, long bytesAlignment) {
+            checkOwnerThread();
+            long min = ((MemoryAddressImpl)segment.baseAddress()).unsafeGetOffset();
+            long start = Utils.alignUp(min + sp, bytesAlignment) - min;
+            try {
+                MemorySegment slice = segment.asSlice(start, bytesSize)
+                        .withAccessModes(SCOPE_MASK);
+                sp = start + bytesSize;
+                return slice.baseAddress();
+            } catch (IndexOutOfBoundsException ex) {
+                throw new OutOfMemoryError("Not enough space left to allocate");
+            }
+        }
+    }
+}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/BoundedAllocationScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/BoundedAllocationScope.java
deleted file mode 100644
index 86bee37d84d..00000000000
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/BoundedAllocationScope.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.foreign;
-
-import jdk.incubator.foreign.NativeAllocationScope;
-import jdk.incubator.foreign.MemoryAddress;
-import jdk.incubator.foreign.MemorySegment;
-
-import java.util.OptionalLong;
-
-public class BoundedAllocationScope extends NativeAllocationScope {
-    private final MemorySegment segment;
-    private long sp = 0L;
-
-    @Override
-    public OptionalLong byteSize() {
-        return OptionalLong.of(segment.byteSize());
-    }
-
-    @Override
-    public long allocatedBytes() {
-        return sp;
-    }
-
-    public BoundedAllocationScope(long size) {
-        this.segment = MemorySegment.allocateNative(size);
-    }
-
-    @Override
-    public MemoryAddress allocate(long bytesSize, long bytesAlignment) {
-        long min = ((MemoryAddressImpl)segment.baseAddress()).unsafeGetOffset();
-        long start = Utils.alignUp(min + sp, bytesAlignment) - min;
-        try {
-            MemorySegment slice = segment.asSlice(start, bytesSize)
-                    .withAccessModes(MemorySegment.READ | MemorySegment.WRITE | MemorySegment.ACQUIRE);
-            sp = start + bytesSize;
-            return slice.baseAddress();
-        } catch (IndexOutOfBoundsException ex) {
-            throw new OutOfMemoryError("Not enough space left to allocate");
-        }
-    }
-
-    @Override
-    public void close() {
-        segment.close();
-    }
-}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/UnboundedAllocationScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/UnboundedAllocationScope.java
deleted file mode 100644
index cc38f75f5f2..00000000000
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/UnboundedAllocationScope.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.foreign;
-
-import jdk.incubator.foreign.NativeAllocationScope;
-import jdk.incubator.foreign.MemoryAddress;
-import jdk.incubator.foreign.MemorySegment;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.OptionalLong;
-import java.util.function.LongFunction;
-
-public class UnboundedAllocationScope extends NativeAllocationScope {
-
-    private static final long BLOCK_SIZE = 4 * 1024;
-    private static final long MAX_ALLOC_SIZE = BLOCK_SIZE / 2;
-
-    private final List<MemorySegment> usedSegments = new ArrayList<>();
-    private MemorySegment segment;
-    private long sp = 0L;
-    private long size = 0L;
-
-    @Override
-    public OptionalLong byteSize() {
-        return OptionalLong.empty();
-    }
-
-    @Override
-    public long allocatedBytes() {
-        return size;
-    }
-
-    public UnboundedAllocationScope() {
-        this.segment = MemorySegment.allocateNative(BLOCK_SIZE);
-    }
-
-    @Override
-    public MemoryAddress allocate(long bytesSize, long bytesAlignment) {
-        if (bytesSize > MAX_ALLOC_SIZE) {
-            MemorySegment segment = MemorySegment.allocateNative(bytesSize, bytesAlignment);
-            usedSegments.add(segment);
-            return segment.withAccessModes(MemorySegment.READ | MemorySegment.WRITE | MemorySegment.ACQUIRE)
-                    .baseAddress();
-        }
-        for (int i = 0; i < 2; i++) {
-            long min = ((MemoryAddressImpl) segment.baseAddress()).unsafeGetOffset();
-            long start = Utils.alignUp(min + sp, bytesAlignment) - min;
-            try {
-                MemorySegment slice = segment.asSlice(start, bytesSize)
-                        .withAccessModes(MemorySegment.READ | MemorySegment.WRITE | MemorySegment.ACQUIRE);
-                sp = start + bytesSize;
-                size += Utils.alignUp(bytesSize, bytesAlignment);
-                return slice.baseAddress();
-            } catch (IndexOutOfBoundsException ex) {
-                sp = 0L;
-                usedSegments.add(segment);
-                segment = MemorySegment.allocateNative(BLOCK_SIZE);
-            }
-        }
-        throw new AssertionError("Cannot get here!");
-    }
-
-    @Override
-    public void close() {
-        segment.close();
-        usedSegments.forEach(MemorySegment::close);
-    }
-}
diff --git a/test/jdk/java/foreign/Cstring.java b/test/jdk/java/foreign/Cstring.java
index fc83c846c8a..8228464e15f 100644
--- a/test/jdk/java/foreign/Cstring.java
+++ b/test/jdk/java/foreign/Cstring.java
@@ -23,7 +23,7 @@
 
 import java.lang.invoke.VarHandle;
 import java.nio.charset.Charset;
-import jdk.incubator.foreign.NativeAllocationScope;
+import jdk.incubator.foreign.NativeScope;
 import jdk.incubator.foreign.MemoryAddress;
 import jdk.incubator.foreign.MemoryLayout;
 import jdk.incubator.foreign.MemorySegment;
@@ -54,7 +54,7 @@ private static MemorySegment toCString(byte[] bytes) {
         return segment;
     }
 
-    private static MemoryAddress toCString(byte[] bytes, NativeAllocationScope scope) {
+    private static MemoryAddress toCString(byte[] bytes, NativeScope scope) {
         MemoryLayout strLayout = MemoryLayout.ofSequence(bytes.length + 1, C_CHAR);
         MemoryAddress addr = scope.allocate(strLayout);
         copy(addr, bytes);
@@ -77,11 +77,11 @@ public static MemorySegment toCString(String str, Charset charset) {
          return toCString(str.getBytes(charset));
     }
 
-    public static MemoryAddress toCString(String str, NativeAllocationScope scope) {
+    public static MemoryAddress toCString(String str, NativeScope scope) {
         return toCString(str.getBytes(), scope);
     }
 
-    public static MemoryAddress toCString(String str, Charset charset, NativeAllocationScope scope) {
+    public static MemoryAddress toCString(String str, Charset charset, NativeScope scope) {
         return toCString(str.getBytes(charset), scope);
     }
 
diff --git a/test/jdk/java/foreign/TestAllocationScope.java b/test/jdk/java/foreign/TestAllocationScope.java
deleted file mode 100644
index 9eaaaa1b616..00000000000
--- a/test/jdk/java/foreign/TestAllocationScope.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
- *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- *  This code is free software; you can redistribute it and/or modify it
- *  under the terms of the GNU General Public License version 2 only, as
- *  published by the Free Software Foundation.
- *
- *  This code is distributed in the hope that it will be useful, but WITHOUT
- *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- *  version 2 for more details (a copy is included in the LICENSE file that
- *  accompanied this code).
- *
- *  You should have received a copy of the GNU General Public License version
- *  2 along with this work; if not, write to the Free Software Foundation,
- *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- *  or visit www.oracle.com if you need additional information or have any
- *  questions.
- *
- */
-
-/*
- * @test
- * @run testng TestAllocationScope
- */
-
-import jdk.incubator.foreign.NativeAllocationScope;
-import jdk.incubator.foreign.MemoryHandles;
-import jdk.incubator.foreign.MemoryLayouts;
-import jdk.incubator.foreign.MemoryLayout;
-import jdk.incubator.foreign.MemoryAddress;
-
-import org.testng.annotations.*;
-
-import java.lang.invoke.VarHandle;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-import static org.testng.Assert.*;
-
-public class TestAllocationScope {
-
-    final static int ELEMS = 128;
-
-    @Test(dataProvider = "allocationScopes")
-    public <Z> void testAllocation(Z value, ScopeFactory scopeFactory, MemoryLayout layout, Class<?> carrier, AllocationFunction<Z> allocationFunction, Function<MemoryLayout, VarHandle> handleFactory) {
-        MemoryLayout[] layouts = {
-                layout,
-                layout.withBitAlignment(layout.bitAlignment() * 2),
-                layout.withBitAlignment(layout.bitAlignment() * 4),
-                layout.withBitAlignment(layout.bitAlignment() * 8)
-        };
-        for (MemoryLayout alignedLayout : layouts) {
-            List<MemoryAddress> addressList = new ArrayList<>();
-            int elems = ELEMS / ((int)alignedLayout.byteAlignment() / (int)layout.byteAlignment());
-            try (NativeAllocationScope scope = scopeFactory.make((int)alignedLayout.byteSize() * ELEMS)) {
-                for (int i = 0 ; i < elems ; i++) {
-                    MemoryAddress address = allocationFunction.allocate(scope, alignedLayout, value);
-                    assertEquals(address.segment().byteSize(), alignedLayout.byteSize());
-                    addressList.add(address);
-                    VarHandle handle = handleFactory.apply(alignedLayout);
-                    assertEquals(value, handle.get(address));
-                    try {
-                        address.segment().close();
-                        fail();
-                    } catch (UnsupportedOperationException uoe) {
-                        //failure is expected
-                        assertTrue(true);
-                    }
-                }
-                boolean isBound = scope.byteSize().isPresent();
-                try {
-                    allocationFunction.allocate(scope, alignedLayout, value); //too much, should fail if bound
-                    assertFalse(isBound);
-                } catch (OutOfMemoryError ex) {
-                    //failure is expected if bound
-                    assertTrue(isBound);
-                }
-            }
-            // addresses should be invalid now
-            for (MemoryAddress address : addressList) {
-                assertFalse(address.segment().isAlive());
-            }
-        }
-    }
-
-    static final int SIZE_256M = 1024 * 1024 * 256;
-
-    @Test
-    public void testBigAllocationInUnboundedScope() {
-        try (NativeAllocationScope scope = NativeAllocationScope.unboundedScope()) {
-            for (int i = 8 ; i < SIZE_256M ; i *= 8) {
-                MemoryAddress address = scope.allocate(i);
-                //check size
-                assertEquals(address.segment().byteSize(), i);
-                //check alignment
-                assertTrue(address.segment().baseAddress().toRawLongValue() % i == 0);
-            }
-        }
-    }
-
-    @DataProvider(name = "allocationScopes")
-    static Object[][] allocationScopes() {
-        return new Object[][] {
-                { (byte)42, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_8_BE, byte.class,
-                        (AllocationFunction<Byte>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
-                { (short)42, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_16_BE, short.class,
-                        (AllocationFunction<Short>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
-                { 42, (ScopeFactory) NativeAllocationScope::boundedScope,
-                        MemoryLayouts.BITS_32_BE, int.class,
-                        (AllocationFunction<Integer>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
-                { 42f, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_32_BE, float.class,
-                        (AllocationFunction<Float>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
-                { 42L, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_BE, long.class,
-                        (AllocationFunction<Long>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
-                { 42d, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_BE, double.class,
-                        (AllocationFunction<Double>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
-                { MemoryAddress.ofLong(42), (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_BE, MemoryAddress.class,
-                        (AllocationFunction<MemoryAddress>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
-
-                { (byte)42, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_8_LE, byte.class,
-                        (AllocationFunction<Byte>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
-                { (short)42, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_16_LE, short.class,
-                        (AllocationFunction<Short>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
-                { 42, (ScopeFactory) NativeAllocationScope::boundedScope,
-                        MemoryLayouts.BITS_32_LE, int.class,
-                        (AllocationFunction<Integer>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
-                { 42f, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_32_LE, float.class,
-                        (AllocationFunction<Float>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
-                { 42L, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_LE, long.class,
-                        (AllocationFunction<Long>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
-                { 42d, (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_LE, double.class,
-                        (AllocationFunction<Double>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
-                { MemoryAddress.ofLong(42), (ScopeFactory) NativeAllocationScope::boundedScope, MemoryLayouts.BITS_64_LE, MemoryAddress.class,
-                        (AllocationFunction<MemoryAddress>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
-
-                { (byte)42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_8_BE, byte.class,
-                        (AllocationFunction<Byte>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
-                { (short)42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_16_BE, short.class,
-                        (AllocationFunction<Short>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
-                { 42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(),
-                        MemoryLayouts.BITS_32_BE, int.class,
-                        (AllocationFunction<Integer>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
-                { 42f, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_32_BE, float.class,
-                        (AllocationFunction<Float>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
-                { 42L, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_BE, long.class,
-                        (AllocationFunction<Long>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
-                { 42d, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_BE, double.class,
-                        (AllocationFunction<Double>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
-                { MemoryAddress.ofLong(42), (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_BE, MemoryAddress.class,
-                        (AllocationFunction<MemoryAddress>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
-
-                { (byte)42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_8_LE, byte.class,
-                        (AllocationFunction<Byte>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
-                { (short)42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_16_LE, short.class,
-                        (AllocationFunction<Short>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
-                { 42, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(),
-                        MemoryLayouts.BITS_32_LE, int.class,
-                        (AllocationFunction<Integer>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
-                { 42f, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_32_LE, float.class,
-                        (AllocationFunction<Float>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
-                { 42L, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_LE, long.class,
-                        (AllocationFunction<Long>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
-                { 42d, (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_LE, double.class,
-                        (AllocationFunction<Double>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
-                { MemoryAddress.ofLong(42), (ScopeFactory)size -> NativeAllocationScope.unboundedScope(), MemoryLayouts.BITS_64_LE, MemoryAddress.class,
-                        (AllocationFunction<MemoryAddress>) NativeAllocationScope::allocate,
-                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
-        };
-    }
-
-    interface AllocationFunction<X> {
-        MemoryAddress allocate(NativeAllocationScope scope, MemoryLayout layout, X value);
-    }
-
-    interface ScopeFactory {
-        NativeAllocationScope make(int size);
-    }
-}
diff --git a/test/jdk/java/foreign/TestNativeScope.java b/test/jdk/java/foreign/TestNativeScope.java
new file mode 100644
index 00000000000..10b82dc4814
--- /dev/null
+++ b/test/jdk/java/foreign/TestNativeScope.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ *  This code is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 only, as
+ *  published by the Free Software Foundation.
+ *
+ *  This code is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ *  version 2 for more details (a copy is included in the LICENSE file that
+ *  accompanied this code).
+ *
+ *  You should have received a copy of the GNU General Public License version
+ *  2 along with this work; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ *  or visit www.oracle.com if you need additional information or have any
+ *  questions.
+ *
+ */
+
+/*
+ * @test
+ * @run testng TestNativeScope
+ */
+
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.NativeScope;
+import jdk.incubator.foreign.MemoryHandles;
+import jdk.incubator.foreign.MemoryLayouts;
+import jdk.incubator.foreign.MemoryLayout;
+import jdk.incubator.foreign.MemoryAddress;
+
+import org.testng.annotations.*;
+
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+import static jdk.incubator.foreign.MemorySegment.CLOSE;
+import static jdk.incubator.foreign.MemorySegment.HANDOFF;
+import static org.testng.Assert.*;
+
+public class TestNativeScope {
+
+    final static int ELEMS = 128;
+
+    @Test(dataProvider = "nativeScopes")
+    public <Z> void testAllocation(Z value, ScopeFactory scopeFactory, MemoryLayout layout, Class<?> carrier, AllocationFunction<Z> allocationFunction, Function<MemoryLayout, VarHandle> handleFactory) {
+        MemoryLayout[] layouts = {
+                layout,
+                layout.withBitAlignment(layout.bitAlignment() * 2),
+                layout.withBitAlignment(layout.bitAlignment() * 4),
+                layout.withBitAlignment(layout.bitAlignment() * 8)
+        };
+        for (MemoryLayout alignedLayout : layouts) {
+            List<MemoryAddress> addressList = new ArrayList<>();
+            int elems = ELEMS / ((int)alignedLayout.byteAlignment() / (int)layout.byteAlignment());
+            try (NativeScope scope = scopeFactory.make((int)alignedLayout.byteSize() * ELEMS)) {
+                for (int i = 0 ; i < elems ; i++) {
+                    MemoryAddress address = allocationFunction.allocate(scope, alignedLayout, value);
+                    assertEquals(address.segment().byteSize(), alignedLayout.byteSize());
+                    addressList.add(address);
+                    VarHandle handle = handleFactory.apply(alignedLayout);
+                    assertEquals(value, handle.get(address));
+                    try {
+                        address.segment().close();
+                        fail();
+                    } catch (UnsupportedOperationException uoe) {
+                        //failure is expected
+                        assertTrue(true);
+                    }
+                }
+                boolean isBound = scope.byteSize().isPresent();
+                try {
+                    allocationFunction.allocate(scope, alignedLayout, value); //too much, should fail if bound
+                    assertFalse(isBound);
+                } catch (OutOfMemoryError ex) {
+                    //failure is expected if bound
+                    assertTrue(isBound);
+                }
+            }
+            // addresses should be invalid now
+            for (MemoryAddress address : addressList) {
+                assertFalse(address.segment().isAlive());
+            }
+        }
+    }
+
+    static final int SIZE_256M = 1024 * 1024 * 256;
+
+    @Test
+    public void testBigAllocationInUnboundedScope() {
+        try (NativeScope scope = NativeScope.unboundedScope()) {
+            for (int i = 8 ; i < SIZE_256M ; i *= 8) {
+                MemoryAddress address = scope.allocate(i);
+                //check size
+                assertEquals(address.segment().byteSize(), i);
+                //check alignment
+                assertTrue(address.segment().baseAddress().toRawLongValue() % i == 0);
+            }
+        }
+    }
+
+    @Test
+    public void testAttachClose() {
+        MemorySegment s1 = MemorySegment.ofArray(new byte[1]);
+        MemorySegment s2 = MemorySegment.ofArray(new byte[1]);
+        MemorySegment s3 = MemorySegment.ofArray(new byte[1]);
+        assertTrue(s1.isAlive());
+        assertTrue(s2.isAlive());
+        assertTrue(s3.isAlive());
+        try (NativeScope scope = NativeScope.boundedScope(10)) {
+            MemorySegment ss1 = scope.register(s1);
+            assertFalse(s1.isAlive());
+            assertTrue(ss1.isAlive());
+            s1 = ss1;
+            MemorySegment ss2 = scope.register(s2);
+            assertFalse(s2.isAlive());
+            assertTrue(ss2.isAlive());
+            s2 = ss2;
+            MemorySegment ss3 = scope.register(s3);
+            assertFalse(s3.isAlive());
+            assertTrue(ss3.isAlive());
+            s3 = ss3;
+        }
+        assertFalse(s1.isAlive());
+        assertFalse(s2.isAlive());
+        assertFalse(s3.isAlive());
+    }
+
+    @Test
+    public void testNoTerminalOps() {
+        try (NativeScope scope = NativeScope.boundedScope(10)) {
+            MemorySegment s1 = MemorySegment.ofArray(new byte[1]);
+            MemorySegment attached = scope.register(s1);
+            int[] terminalOps = {CLOSE, HANDOFF};
+            for (int mode : terminalOps) {
+                if (attached.hasAccessModes(mode)) {
+                    fail();
+                }
+            }
+        }
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testNoReattach() {
+        MemorySegment s1 = MemorySegment.ofArray(new byte[1]);
+        NativeScope scope1 = NativeScope.boundedScope(10);
+        NativeScope scope2 = NativeScope.boundedScope(10);
+        scope2.register(scope1.register(s1));
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testNullClaim() {
+        NativeScope.boundedScope(10).register(null);
+    }
+
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testNotAliveClaim() {
+        MemorySegment segment = MemorySegment.ofArray(new byte[1]);
+        segment.close();
+        NativeScope.boundedScope(10).register(segment);
+    }
+
+    @Test
+    public void testNoClaimFromWrongThread() throws InterruptedException {
+        MemorySegment s = MemorySegment.ofArray(new byte[1]);
+        AtomicBoolean failed = new AtomicBoolean(false);
+        Thread t = new Thread(() -> {
+            try {
+                NativeScope.boundedScope(10).register(s);
+            } catch (IllegalArgumentException ex) {
+                failed.set(true);
+            }
+        });
+        t.start();
+        t.join();
+        assertTrue(failed.get());
+    }
+
+    @DataProvider(name = "nativeScopes")
+    static Object[][] nativeScopes() {
+        return new Object[][] {
+                { (byte)42, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_8_BE, byte.class,
+                        (AllocationFunction<Byte>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
+                { (short)42, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_16_BE, short.class,
+                        (AllocationFunction<Short>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
+                { 42, (ScopeFactory) NativeScope::boundedScope,
+                        MemoryLayouts.BITS_32_BE, int.class,
+                        (AllocationFunction<Integer>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
+                { 42f, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_32_BE, float.class,
+                        (AllocationFunction<Float>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
+                { 42L, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_BE, long.class,
+                        (AllocationFunction<Long>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
+                { 42d, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_BE, double.class,
+                        (AllocationFunction<Double>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
+                { MemoryAddress.ofLong(42), (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_BE, MemoryAddress.class,
+                        (AllocationFunction<MemoryAddress>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
+
+                { (byte)42, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_8_LE, byte.class,
+                        (AllocationFunction<Byte>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
+                { (short)42, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_16_LE, short.class,
+                        (AllocationFunction<Short>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
+                { 42, (ScopeFactory) NativeScope::boundedScope,
+                        MemoryLayouts.BITS_32_LE, int.class,
+                        (AllocationFunction<Integer>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
+                { 42f, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_32_LE, float.class,
+                        (AllocationFunction<Float>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
+                { 42L, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_LE, long.class,
+                        (AllocationFunction<Long>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
+                { 42d, (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_LE, double.class,
+                        (AllocationFunction<Double>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
+                { MemoryAddress.ofLong(42), (ScopeFactory) NativeScope::boundedScope, MemoryLayouts.BITS_64_LE, MemoryAddress.class,
+                        (AllocationFunction<MemoryAddress>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
+
+                { (byte)42, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_8_BE, byte.class,
+                        (AllocationFunction<Byte>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
+                { (short)42, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_16_BE, short.class,
+                        (AllocationFunction<Short>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
+                { 42, (ScopeFactory)size -> NativeScope.unboundedScope(),
+                        MemoryLayouts.BITS_32_BE, int.class,
+                        (AllocationFunction<Integer>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
+                { 42f, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_32_BE, float.class,
+                        (AllocationFunction<Float>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
+                { 42L, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_BE, long.class,
+                        (AllocationFunction<Long>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
+                { 42d, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_BE, double.class,
+                        (AllocationFunction<Double>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
+                { MemoryAddress.ofLong(42), (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_BE, MemoryAddress.class,
+                        (AllocationFunction<MemoryAddress>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
+
+                { (byte)42, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_8_LE, byte.class,
+                        (AllocationFunction<Byte>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(byte.class) },
+                { (short)42, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_16_LE, short.class,
+                        (AllocationFunction<Short>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(short.class) },
+                { 42, (ScopeFactory)size -> NativeScope.unboundedScope(),
+                        MemoryLayouts.BITS_32_LE, int.class,
+                        (AllocationFunction<Integer>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(int.class) },
+                { 42f, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_32_LE, float.class,
+                        (AllocationFunction<Float>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(float.class) },
+                { 42L, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_LE, long.class,
+                        (AllocationFunction<Long>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(long.class) },
+                { 42d, (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_LE, double.class,
+                        (AllocationFunction<Double>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> l.varHandle(double.class) },
+                { MemoryAddress.ofLong(42), (ScopeFactory)size -> NativeScope.unboundedScope(), MemoryLayouts.BITS_64_LE, MemoryAddress.class,
+                        (AllocationFunction<MemoryAddress>) NativeScope::allocate,
+                        (Function<MemoryLayout, VarHandle>)l -> MemoryHandles.asAddressVarHandle(l.varHandle(long.class)) },
+        };
+    }
+
+    interface AllocationFunction<X> {
+        MemoryAddress allocate(NativeScope scope, MemoryLayout layout, X value);
+    }
+
+    interface ScopeFactory {
+        NativeScope make(int size);
+    }
+}