You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by le...@apache.org on 2021/11/18 19:11:23 UTC

[datasketches-memory] 01/01: This is a mess and has errors, so it will not compile.

This is an automated email from the ASF dual-hosted git repository.

leerho pushed a commit to branch Jdk17_Panama_mess
in repository https://gitbox.apache.org/repos/asf/datasketches-memory.git

commit ad382bccf0ac35dc0beb6bfe7f541f9b24062384
Author: Lee Rhodes <le...@users.noreply.github.com>
AuthorDate: Thu Nov 18 11:10:57 2021 -0800

    This is a mess and has errors, so it will not compile.
---
 datasketches-memory-java17/pom.xml                 |  10 +
 .../src/main/java/module-info.java                 |   2 +-
 .../org/apache/datasketches/memory/BaseState.java  | 265 +++++++++
 .../org/apache/datasketches/memory/Memory.java     | 490 ++++++++++++++++
 .../{internal/Dummy.java => MemoryException.java}  |  21 +-
 .../datasketches/memory/MemoryRequestServer.java   |  50 ++
 .../Dummy.java => ReadOnlyException.java}          |  17 +-
 .../apache/datasketches/memory/WritableMemory.java | 642 +++++++++++++++++++++
 .../memory/internal/BaseStateImpl.java             | 491 ++++++++++++++++
 .../memory/internal/CompareAndCopy.java            | 522 +++++++++++++++++
 .../datasketches/memory/internal/UnsafeUtil.java   | 228 ++++++++
 .../apache/datasketches/memory/internal/Util.java  |  24 +-
 .../{internal/Dummy.java => package-info.java}     |   9 +-
 .../memory/internal/AllocateDirectMap.java         |   6 +-
 .../memory/internal/MemoryCleaner.java             |   7 +-
 .../apache/datasketches/memory/internal/Util.java  |   2 +-
 .../src/main/java/module-info.java                 |   2 +-
 .../java/org/apache/datasketches/memory/Map.java   |  25 +-
 .../datasketches/memory/MemoryException.java       |  21 +-
 .../memory/internal/MemoryCleaner.java             |  38 +-
 .../apache/datasketches/memory/package-info.java   |   9 +-
 21 files changed, 2811 insertions(+), 70 deletions(-)

diff --git a/datasketches-memory-java17/pom.xml b/datasketches-memory-java17/pom.xml
index 2a24576..599f84a 100644
--- a/datasketches-memory-java17/pom.xml
+++ b/datasketches-memory-java17/pom.xml
@@ -28,6 +28,15 @@
         <version>2.1.0-SNAPSHOT</version>
     </parent>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.datasketches</groupId>
+            <artifactId>datasketches-memory-java8</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
     <artifactId>datasketches-memory-java17</artifactId>
     <name>${project.artifactId}</name>
     <packaging>jar</packaging>
@@ -39,4 +48,5 @@
         <maven.compiler.target>${java.version}</maven.compiler.target>
     </properties>
 
+
 </project>
diff --git a/datasketches-memory-java17/src/main/java/module-info.java b/datasketches-memory-java17/src/main/java/module-info.java
index ed6cf80..c19a017 100644
--- a/datasketches-memory-java17/src/main/java/module-info.java
+++ b/datasketches-memory-java17/src/main/java/module-info.java
@@ -22,5 +22,5 @@ module org.apache.datasketches.memory {
     requires java.logging;
     requires jdk.unsupported;
     requires jdk.incubator.foreign;
-    exports org.apache.datasketches.memory.internal;
+    exports org.apache.datasketches.memory;
 }
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/BaseState.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/BaseState.java
new file mode 100644
index 0000000..2f0cd96
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/BaseState.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.datasketches.memory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.apache.datasketches.memory.internal.BaseStateImpl;
+
+/**
+ * Keeps key configuration state for Memory and Buffer plus some common static variables
+ * and check methods.
+ *
+ * @author Lee Rhodes
+ */
+public interface BaseState {
+  /**
+   * The placeholder for the default MemoryRequestServer, if set at all.
+   */
+  static final MemoryRequestServer defaultMemReqSvr = null; //new DefaultMemoryRequestServer();
+
+  //Byte Order Related
+
+  /**
+   * Gets the current Type ByteOrder.
+   * This may be different from the ByteOrder of the backing resource and of the Native Byte Order.
+   * @return the current Type ByteOrder.
+   */
+  ByteOrder getTypeByteOrder();
+
+  /**
+   * Returns true if the Native ByteOrder is the same as the ByteOrder of the
+   * current Buffer or Memory and the same ByteOrder as the given byteOrder.
+   * @param byteOrder the given ByteOrder
+   * @return true if the Native ByteOrder is the same as the ByteOrder of the
+   * current Buffer or Memory and the same ByteOrder as the given byteOrder.
+   */
+  boolean isByteOrderCompatible(ByteOrder byteOrder);
+
+  /**
+   * Returns true if the given object is an instance of this class and has equal data contents.
+   * @param that the given object
+   * @return true if the given Object is an instance of this class and has equal data contents.
+   */
+  @Override
+  boolean equals(Object that);
+
+  /**
+   * Returns true if the given object is an instance of this class and has equal contents to
+   * this object in the given range of bytes. This will also check two distinct ranges within the
+   * same object for equals.
+   * @param thisOffsetBytes the starting offset in bytes for this object.
+   * @param that the given object
+   * @param thatOffsetBytes the starting offset in bytes for the given object
+   * @param lengthBytes the size of the range in bytes
+   * @return true if the given object has equal contents to this object in the given range of
+   * bytes.
+   */
+  boolean equalTo(long thisOffsetBytes, Object that,
+      long thatOffsetBytes, long lengthBytes);
+
+  /**
+   * Gets the backing ByteBuffer if it exists, otherwise returns null.
+   * @return the backing ByteBuffer if it exists, otherwise returns null.
+   */
+  ByteBuffer getByteBuffer();
+
+  /**
+   * Gets the capacity of this object in bytes
+   * @return the capacity of this object in bytes
+   */
+  long getCapacity();
+
+  /**
+   * Gets the cumulative offset in bytes of this object from the backing resource.
+   * This offset may also include other offset components such as the native off-heap
+   * memory address, DirectByteBuffer split offsets, region offsets, and unsafe arrayBaseOffsets.
+   *
+   * @return the cumulative offset in bytes of this object from the backing resource.
+   */
+  long getCumulativeOffset();
+
+  /**
+   * Gets the cumulative offset in bytes of this object from the backing resource including the given
+   * offsetBytes. This offset may also include other offset components such as the native off-heap
+   * memory address, DirectByteBuffer split offsets, region offsets, and unsafe arrayBaseOffsets.
+   *
+   * @param offsetBytes offset to be added to the cumulative offset.
+   * @return the cumulative offset in bytes of this object from the backing resource including the
+   * given offsetBytes.
+   */
+  long getCumulativeOffset(long offsetBytes);
+
+  /**
+   * Returns the offset of address zero of this object relative to the address zero of the
+   * backing resource but not including the size of any Java object header.
+   * @return the offset of address zero of this object relative to the address zero of the
+   * backing resource but not including the size of any Java object header.
+   */
+  long getRegionOffset();
+
+  /**
+   * Returns the offset of address zero of this object relative to the address zero of the
+   * backing resource plus the given offsetBytes but not including the size of any Java object
+   * header.
+   * @param offsetBytes the given offsetBytes
+   * @return the offset of address zero of this object relative to the address zero of the
+   * backing resource plus the given offsetBytes but not including the size of any Java object
+   * header.
+   */
+  long getRegionOffset(long offsetBytes);
+
+  /**
+   * Returns true if this object is backed by an on-heap primitive array
+   * @return true if this object is backed by an on-heap primitive array
+   */
+  boolean hasArray();
+
+  /**
+   * Returns the hashCode of this object.
+   *
+   * <p>The hash code of this object depends upon all of its contents.
+   * Because of this, it is inadvisable to use these objects as keys in hash maps
+   * or similar data structures unless it is known that their contents will not change.</p>
+   *
+   * <p>If it is desirable to use these objects in a hash map depending only on object identity,
+   * than the {@link java.util.IdentityHashMap} can be used.</p>
+   *
+   * @return the hashCode of this object.
+   */
+  @Override
+  int hashCode();
+
+  /**
+   * Returns the 64-bit hash of the sequence of bytes in this object specified by
+   * <i>offsetBytes</i>, <i>lengthBytes</i> and a <i>seed</i>.  Note that the sequence of bytes is
+   * always processed in the same order independent of endianness.
+   *
+   * @param offsetBytes the given offset in bytes to the first byte of the byte sequence.
+   * @param lengthBytes the given length in bytes of the byte sequence.
+   * @param seed the given long seed.
+   * @return the 64-bit hash of the sequence of bytes in this object specified by
+   * <i>offsetBytes</i> and <i>lengthBytes</i>.
+   */
+  long xxHash64(long offsetBytes, long lengthBytes, long seed);
+
+  /**
+   * Returns a 64-bit hash from a single long. This method has been optimized for speed when only
+   * a single hash of a long is required.
+   * @param in A long.
+   * @param seed A long valued seed.
+   * @return the hash.
+   */
+  long xxHash64(long in, long seed);
+
+  /**
+   * Returns true if this Memory is backed by a ByteBuffer.
+   * @return true if this Memory is backed by a ByteBuffer.
+   */
+  boolean hasByteBuffer();
+
+  /**
+   * Returns true if the backing resource is direct (off-heap) memory.
+   * This is the case for allocated direct memory, memory mapped files,
+   * @return true if the backing resource is direct (off-heap) memory.
+   */
+  boolean isDirect();
+
+  /**
+   * Returns true if this object or the backing resource is read-only.
+   * @return true if this object or the backing resource is read-only.
+   */
+  boolean isReadOnly();
+
+  /**
+   * Returns true if the backing resource of <i>this</i> is identical with the backing resource
+   * of <i>that</i>. The capacities must be the same.  If <i>this</i> is a region,
+   * the region offset must also be the same.
+   * @param that A different non-null object
+   * @return true if the backing resource of <i>this</i> is the same as the backing resource
+   * of <i>that</i>.
+   */
+  boolean isSameResource(Object that);
+
+  /**
+   * Returns true if this object is valid and has not been closed.
+   * This is relevant only for direct (off-heap) memory and Mapped Files.
+   * @return true if this object is valid and has not been closed.
+   */
+  boolean isValid();
+
+  /**
+   * Checks that the specified range of bytes is within bounds of this object, throws
+   * {@link IllegalArgumentException} if it's not: i. e. if offsetBytes &lt; 0, or length &lt; 0,
+   * or offsetBytes + length &gt; {@link #getCapacity()}.
+   * @param offsetBytes the given offset in bytes of this object
+   * @param lengthBytes the given length in bytes of this object
+   */
+  void checkValidAndBounds(long offsetBytes, long lengthBytes);
+
+  //Monitoring
+
+  /**
+   * Gets the current number of active direct memory allocations.
+   * @return the current number of active direct memory allocations.
+   */
+  static long getCurrentDirectMemoryAllocations() {
+    return BaseStateImpl.getCurrentDirectMemoryAllocations();
+  }
+
+  /**
+   * Gets the current size of active direct memory allocated.
+   * @return the current size of active direct memory allocated.
+   */
+  static long getCurrentDirectMemoryAllocated() {
+    return BaseStateImpl.getCurrentDirectMemoryAllocated();
+  }
+
+  /**
+   * Gets the current number of active direct memory map allocations.
+   * @return the current number of active direct memory map allocations.
+   */
+  static long getCurrentDirectMemoryMapAllocations() {
+    return BaseStateImpl.getCurrentDirectMemoryMapAllocations();
+  }
+
+  /**
+   * Gets the current size of active direct memory map allocated.
+   * @return the current size of active direct memory map allocated.
+   */
+  static long getCurrentDirectMemoryMapAllocated() {
+    return BaseStateImpl.getCurrentDirectMemoryMapAllocated();
+  }
+
+  //TO STRING
+
+  /**
+   * Returns a formatted hex string of a range of this object.
+   * Used primarily for testing.
+   * @param header a descriptive header
+   * @param offsetBytes offset bytes relative to this object start
+   * @param lengthBytes number of bytes to convert to a hex string
+   * @return a formatted hex string in a human readable array
+   */
+  String toHexString(String header, long offsetBytes, int lengthBytes);
+
+}
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/Memory.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/Memory.java
new file mode 100644
index 0000000..f25de32
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/Memory.java
@@ -0,0 +1,490 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.datasketches.memory;
+
+import static org.apache.datasketches.memory.internal.Util.negativeCheck;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.WritableByteChannel;
+import java.util.Objects;
+
+import org.apache.datasketches.memory.internal.BaseWritableMemoryImpl;
+import org.apache.datasketches.memory.internal.Prim;
+import org.apache.datasketches.memory.internal.UnsafeUtil;
+
+/**
+ * Defines the read-only API for offset access to a resource.
+ *
+ * @author Lee Rhodes
+ */
+public interface Memory extends BaseState {
+
+  //BYTE BUFFER
+
+  /**
+   * Accesses the given <i>ByteBuffer</i> for read-only operations. The returned <i>Memory</i> object has
+   * the same byte order, as the given <i>ByteBuffer</i>.
+   * @param byteBuffer the given <i>ByteBuffer</i>. It must be non-null and with capacity &ge; 0.
+   * @return a new <i>Memory</i> for read-only operations on the given <i>ByteBuffer</i>.
+   */
+  static Memory wrap(ByteBuffer byteBuffer) {
+    return wrap(byteBuffer, byteBuffer.order());
+  }
+
+  /**
+   * Accesses the given <i>ByteBuffer</i> for read-only operations. The returned <i>Memory</i> object has
+   * the given byte order, ignoring the byte order of the given <i>ByteBuffer</i> for future reads and writes.
+   * @param byteBuffer the given <i>ByteBuffer</i>. It must be non-null and with capacity &ge; 0.
+   * @param byteOrder the byte order to be used.  It must be non-null.
+   * @return a new <i>Memory</i> for read-only operations on the given <i>ByteBuffer</i>.
+   */
+  static Memory wrap(ByteBuffer byteBuffer, ByteOrder byteOrder) {
+    Objects.requireNonNull(byteBuffer, "byteBuffer must not be null");
+    Objects.requireNonNull(byteOrder, "byteOrder must not be null");
+    negativeCheck(byteBuffer.capacity(), "byteBuffer.capacity()");
+    return BaseWritableMemoryImpl.wrapByteBuffer(byteBuffer, true, byteOrder, null);
+  }
+
+  //MAP
+  /**
+   * Maps the entire given file into native-ordered <i>Memory</i> for read operations
+   * Calling this method is equivalent to calling
+   * {@link #map(File, long, long, ByteOrder) map(file, 0, file.length(), ByteOrder.nativeOrder())}.
+   * @param file the given file to map. It must be non-null, length &ge; 0, and readable.
+   * @return <i>MapHandle</i> for managing the mapped memory.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static MapHandle map(File file) {
+    return map(file, 0, file.length(), ByteOrder.nativeOrder());
+  }
+
+  /**
+   * Maps the specified portion of the given file into <i>Memory</i> for read operations.
+   * @param file the given file to map. It must be non-null,readable and length &ge; 0.
+   * @param fileOffsetBytes the position in the given file in bytes. It must not be negative.
+   * @param capacityBytes the size of the mapped memory. It must be &ge; 0.
+   * @param byteOrder the byte order to be used for the mapped memory. It must be non-null.
+   * @return <i>MapHandle</i> for managing the mapped memory.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static MapHandle map(File file, long fileOffsetBytes, long capacityBytes, ByteOrder byteOrder) {
+    Objects.requireNonNull(file, "file must be non-null.");
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null.");
+    if (!file.canRead()) { throw new IllegalArgumentException("file must be readable."); }
+    negativeCheck(file.length(), "file.length()");
+    negativeCheck(fileOffsetBytes, "fileOffsetBytes");
+    negativeCheck(capacityBytes, "capacityBytes");
+    return (MapHandle) BaseWritableMemoryImpl.wrapMap(file, fileOffsetBytes, capacityBytes, true, byteOrder);
+  }
+
+  //REGIONS
+  /**
+   * A region is a read-only view of this object.
+   * <ul>
+   * <li>Returned object's origin = this object's origin + offsetBytes</li>
+   * <li>Returned object's capacity = capacityBytes</li>
+   * </ul>
+   * @param offsetBytes the starting offset with respect to the origin of this <i>Memory</i>. It must be &ge; 0.
+   * @param capacityBytes the capacity of the region in bytes. It must be &ge; 0.
+   * @return a new <i>Memory</i> representing the defined region based on the given
+   * offsetBytes and capacityBytes.
+   */
+  default Memory region(long offsetBytes, long capacityBytes) {
+    return region(offsetBytes, capacityBytes, ByteOrder.nativeOrder());
+  }
+
+  /**
+   * A region is a read-only view of this object.
+   * <ul>
+   * <li>Returned object's origin = this object's origin + <i>offsetBytes</i></li>
+   * <li>Returned object's capacity = <i>capacityBytes</i></li>
+   * <li>Returned object's byte order = <i>byteOrder</i></li>
+   * </ul>
+   * @param offsetBytes the starting offset with respect to the origin of this Memory. It must be &ge; 0.
+   * @param capacityBytes the capacity of the region in bytes. It must be &ge; 0.
+   * @param byteOrder the given byte order. It must be non-null.
+   * @return a new <i>Memory</i> representing the defined region based on the given
+   * offsetBytes, capacityBytes and byteOrder.
+   */
+  Memory region(long offsetBytes, long capacityBytes, ByteOrder byteOrder);
+
+  //AS BUFFER
+  /**
+   * Returns a new <i>Buffer</i> view of this object.
+   * <ul>
+   * <li>Returned object's origin = this object's origin</li>
+   * <li>Returned object's <i>start</i> = 0</li>
+   * <li>Returned object's <i>position</i> = 0</li>
+   * <li>Returned object's <i>end</i> = this object's capacity</li>
+   * <li>Returned object's <i>capacity</i> = this object's capacity</li>
+   * <li>Returned object's <i>start</i>, <i>position</i> and <i>end</i> are mutable</li>
+   * </ul>
+   * @return a new <i>Buffer</i>
+   */
+  default Buffer asBuffer() {
+    return asBuffer(ByteOrder.nativeOrder());
+  }
+
+  /**
+   * Returns a new <i>Buffer</i> view of this object, with the given
+   * byte order.
+   * <ul>
+   * <li>Returned object's origin = this object's origin</li>
+   * <li>Returned object's <i>start</i> = 0</li>
+   * <li>Returned object's <i>position</i> = 0</li>
+   * <li>Returned object's <i>end</i> = this object's capacity</li>
+   * <li>Returned object's <i>capacity</i> = this object's capacity</li>
+   * <li>Returned object's <i>start</i>, <i>position</i> and <i>end</i> are mutable</li>
+   * </ul>
+   * @param byteOrder the given byte order
+   * @return a new <i>Buffer</i> with the given byteOrder.
+   */
+  Buffer asBuffer(ByteOrder byteOrder);
+
+  //ACCESS PRIMITIVE HEAP ARRAYS for readOnly
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and with length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(byte[] array) {
+    Objects.requireNonNull(array, "array must be non-null");
+    return wrap(array, 0, array.length, ByteOrder.nativeOrder());
+  }
+
+  /**
+   * Wraps the given primitive array for read operations with the given byte order.
+   * @param array the given primitive array.
+   * @param byteOrder the byte order to be used
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(byte[] array, ByteOrder byteOrder) {
+    return wrap(array, 0, array.length, byteOrder);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations with the given byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @param offsetBytes the byte offset into the given array
+   * @param lengthBytes the number of bytes to include from the given array, it must be &ge; 0.
+   * @param byteOrder the byte order to be used
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(byte[] array, int offsetBytes, int lengthBytes, ByteOrder byteOrder) {
+    Objects.requireNonNull(array, "array must be non-null");
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null");
+    negativeCheck(offsetBytes, "offsetBytes");
+    negativeCheck(lengthBytes, "lengthBytes");
+    UnsafeUtil.checkBounds(offsetBytes, lengthBytes, array.length);
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(boolean[] array) {
+    Objects.requireNonNull(array, "array must be non-null");
+    final long lengthBytes = array.length << Prim.BOOLEAN.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(char[] array) {
+    Objects.requireNonNull(array, "array must be non-null");
+    final long lengthBytes = array.length << Prim.CHAR.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(short[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.SHORT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(int[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.INT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(long[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.LONG.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(float[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.FLOAT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for read operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null and length &ge; 0.
+   * @return a new <i>Memory</i> for read operations
+   */
+  static Memory wrap(double[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.DOUBLE.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, true, ByteOrder.nativeOrder(), null);
+  }
+
+  //PRIMITIVE getX() and getXArray()
+  /**
+   * Gets the boolean value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the boolean at the given offset
+   */
+  boolean getBoolean(long offsetBytes);
+
+  /**
+   * Gets the boolean array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetBooleans offset in array units
+   * @param lengthBooleans number of array units to transfer
+   */
+  void getBooleanArray(long offsetBytes, boolean[] dstArray, int dstOffsetBooleans, int lengthBooleans);
+
+  /**
+   * Gets the byte value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the byte at the given offset
+   */
+  byte getByte(long offsetBytes);
+
+  /**
+   * Gets the byte array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetBytes offset in array units
+   * @param lengthBytes number of array units to transfer
+   */
+  void getByteArray(long offsetBytes, byte[] dstArray, int dstOffsetBytes, int lengthBytes);
+
+  /**
+   * Gets the char value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the char at the given offset
+   */
+  char getChar(long offsetBytes);
+
+  /**
+   * Gets the char array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetChars offset in array units
+   * @param lengthChars number of array units to transfer
+   */
+  void getCharArray(long offsetBytes, char[] dstArray, int dstOffsetChars, int lengthChars);
+
+  /**
+   * Gets UTF-8 encoded bytes from this Memory, starting at offsetBytes to a length of
+   * utf8LengthBytes, decodes them into characters and appends them to the given Appendable.
+   * This is specifically designed to reduce the production of intermediate objects (garbage),
+   * thus significantly reducing pressure on the JVM Garbage Collector.
+   * @param offsetBytes offset bytes relative to the Memory start
+   * @param utf8LengthBytes the number of encoded UTF-8 bytes to decode. It is assumed that the
+   * caller has the correct number of utf8 bytes required to decode the number of characters
+   * to be appended to dst. Characters outside the ASCII range can require 2, 3 or 4 bytes per
+   * character to decode.
+   * @param dst the destination Appendable to append the decoded characters to.
+   * @return the number of characters decoded
+   * @throws IOException if dst.append() throws IOException
+   * @throws Utf8CodingException in case of malformed or illegal UTF-8 input
+   */
+  int getCharsFromUtf8(long offsetBytes, int utf8LengthBytes, Appendable dst)
+      throws IOException, Utf8CodingException;
+
+  /**
+   * Gets UTF-8 encoded bytes from this Memory, starting at offsetBytes to a length of
+   * utf8LengthBytes, decodes them into characters and appends them to the given StringBuilder.
+   * This method does *not* reset the length of the destination StringBuilder before appending
+   * characters to it.
+   * This is specifically designed to reduce the production of intermediate objects (garbage),
+   * thus significantly reducing pressure on the JVM Garbage Collector.
+   * @param offsetBytes offset bytes relative to the Memory start
+   * @param utf8LengthBytes the number of encoded UTF-8 bytes to decode. It is assumed that the
+   * caller has the correct number of utf8 bytes required to decode the number of characters
+   * to be appended to dst. Characters outside the ASCII range can require 2, 3 or 4 bytes per
+   * character to decode.
+   * @param dst the destination StringBuilder to append decoded characters to.
+   * @return the number of characters decoded.
+   * @throws Utf8CodingException in case of malformed or illegal UTF-8 input
+   */
+  int getCharsFromUtf8(long offsetBytes, int utf8LengthBytes, StringBuilder dst)
+      throws Utf8CodingException;
+
+  /**
+   * Gets the double value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the double at the given offset
+   */
+  double getDouble(long offsetBytes);
+
+  /**
+   * Gets the double array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetDoubles offset in array units
+   * @param lengthDoubles number of array units to transfer
+   */
+  void getDoubleArray(long offsetBytes, double[] dstArray, int dstOffsetDoubles, int lengthDoubles);
+
+  /**
+   * Gets the float value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the float at the given offset
+   */
+  float getFloat(long offsetBytes);
+
+  /**
+   * Gets the float array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetFloats offset in array units
+   * @param lengthFloats number of array units to transfer
+   */
+  void getFloatArray(long offsetBytes, float[] dstArray, int dstOffsetFloats, int lengthFloats);
+
+  /**
+   * Gets the int value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the int at the given offset
+   */
+  int getInt(long offsetBytes);
+
+  /**
+   * Gets the int array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetInts offset in array units
+   * @param lengthInts number of array units to transfer
+   */
+  void getIntArray(long offsetBytes, int[] dstArray, int dstOffsetInts, int lengthInts);
+
+  /**
+   * Gets the long value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the long at the given offset
+   */
+  long getLong(long offsetBytes);
+
+  /**
+   * Gets the long array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetLongs offset in array units
+   * @param lengthLongs number of array units to transfer
+   */
+  void getLongArray(long offsetBytes, long[] dstArray, int dstOffsetLongs, int lengthLongs);
+
+  /**
+   * Gets the short value at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @return the short at the given offset
+   */
+  short getShort(long offsetBytes);
+
+  /**
+   * Gets the short array at the given offset
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param dstArray The preallocated destination array.
+   * @param dstOffsetShorts offset in array units
+   * @param lengthShorts number of array units to transfer
+   */
+  void getShortArray(long offsetBytes, short[] dstArray, int dstOffsetShorts, int lengthShorts);
+
+  //SPECIAL PRIMITIVE READ METHODS: compareTo, copyTo, writeTo
+  /**
+   * Compares the bytes of this Memory to <i>that</i> Memory.
+   * Returns <i>(this &lt; that) ? (some negative value) : (this &gt; that) ? (some positive value)
+   * : 0;</i>.
+   * If all bytes are equal up to the shorter of the two lengths, the shorter length is considered
+   * to be less than the other.
+   * @param thisOffsetBytes the starting offset for <i>this Memory</i>
+   * @param thisLengthBytes the length of the region to compare from <i>this Memory</i>
+   * @param that the other Memory to compare with
+   * @param thatOffsetBytes the starting offset for <i>that Memory</i>
+   * @param thatLengthBytes the length of the region to compare from <i>that Memory</i>
+   * @return <i>(this &lt; that) ? (some negative value) : (this &gt; that) ? (some positive value)
+   * : 0;</i>
+   */
+  int compareTo(long thisOffsetBytes, long thisLengthBytes, Memory that,
+      long thatOffsetBytes, long thatLengthBytes);
+
+  /**
+   * Copies bytes from a source range of this Memory to a destination range of the given Memory
+   * with the same semantics when copying between overlapping ranges of bytes as method
+   * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} has. However, if the source
+   * and the destination ranges are exactly the same, this method throws {@link
+   * IllegalArgumentException}, because it should never be needed in real-world scenarios and
+   * therefore indicates a bug.
+   * @param srcOffsetBytes the source offset for this Memory
+   * @param destination the destination Memory, which may not be Read-Only.
+   * @param dstOffsetBytes the destination offset
+   * @param lengthBytes the number of bytes to copy
+   */
+  void copyTo(long srcOffsetBytes, WritableMemory destination, long dstOffsetBytes, long lengthBytes);
+
+  /**
+   * Writes bytes from a source range of this Memory to the given {@code WritableByteChannel}.
+   * @param offsetBytes the source offset for this Memory
+   * @param lengthBytes the number of bytes to copy
+   * @param out the destination WritableByteChannel
+   * @throws IOException may occur while writing to the WritableByteChannel
+   */
+  void writeTo(long offsetBytes, long lengthBytes, WritableByteChannel out)
+      throws IOException;
+
+}
+
+
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryException.java
similarity index 55%
copy from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
copy to datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryException.java
index aefdcba..6b3663c 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryException.java
@@ -17,11 +17,26 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
+package org.apache.datasketches.memory;
 
 /**
- * Temporary class & placeholder. It may not be needed.
+ * Specific RuntimeExceptions for the datasketches-memory component.
+ *
+ * @author Lee Rhodes
+ *
  */
-public class Dummy {
+public class MemoryException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
 
+  /**
+   * Constructs a new runtime exception with the specified detail message. The cause is not
+   * initialized, and may subsequently be initialized by a call to
+   * Throwable.initCause(java.lang.Throwable).
+   *
+   * @param message the detail message. The detail message is saved for later retrieval by the
+   * Throwable.getMessage() method.
+   */
+  public MemoryException(final String message) {
+    super(message);
+  }
 }
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryRequestServer.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryRequestServer.java
new file mode 100644
index 0000000..e2ca235
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/MemoryRequestServer.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.memory;
+
+/**
+ * The MemoryRequestServer is a callback interface to provide a means for direct (off-heap), heap and ByteBuffer
+ * backed resources to request more memory.
+ *
+ * @author Lee Rhodes
+ */
+public interface MemoryRequestServer {
+
+  /**
+   * Request new WritableMemory with the given capacity. The current Writable Memory will be used to
+   * determine the byte order of the returned WritableMemory and other checks.
+   * @param currentWritableMemory the current writableMemory of the client. It must be non-null.
+   * @param capacityBytes The capacity being requested. It must be &ge; 0.
+   *
+   * @return new WritableMemory with the given capacity.
+   */
+  WritableMemory request(WritableMemory currentWritableMemory, long capacityBytes);
+
+  /**
+   * Request close the AutoCloseable resource. This only applies to resources allocated using
+   * WritableMemory.allocateDirect(...).
+   * This may be ignored depending on the application implementation.
+   * @param memToClose the relevant WritbleMemory to be considered for closing. It must be non-null.
+   * @param newMemory the newly allocated WritableMemory. It must be non-null.
+   * This is returned from the client to facilitate tracking for the convenience of the resource owner.
+   */
+  void requestClose(final WritableMemory memToClose, WritableMemory newMemory);
+
+}
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/ReadOnlyException.java
similarity index 65%
copy from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
copy to datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/ReadOnlyException.java
index aefdcba..3e14795 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/ReadOnlyException.java
@@ -17,11 +17,22 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
+package org.apache.datasketches.memory;
 
 /**
- * Temporary class & placeholder. It may not be needed.
+ * The exception thrown when attempting to write into a read-only Resource.
+ *
+ * @author Praveenkumar Venkatesan
  */
-public class Dummy {
+public class ReadOnlyException extends MemoryException {
+    private static final long serialVersionUID = 1L;
 
+    /**
+     * Read Only Exception
+     * @param message the error message
+     */
+    public ReadOnlyException(final String message) {
+      super(message);
+    }
 }
+
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/WritableMemory.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/WritableMemory.java
new file mode 100644
index 0000000..14023e1
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/WritableMemory.java
@@ -0,0 +1,642 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.memory;
+
+import static org.apache.datasketches.memory.internal.Util.negativeCheck;
+import static org.apache.datasketches.memory.internal.Util.zeroCheck;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+import org.apache.datasketches.memory.internal.BaseWritableMemoryImpl;
+import org.apache.datasketches.memory.internal.Prim;
+import org.apache.datasketches.memory.internal.UnsafeUtil;
+
+/**
+ * Defines the writable API for offset access to a resource.
+ *
+ * @author Lee Rhodes
+ */
+public interface WritableMemory extends Memory {
+
+  //BYTE BUFFER
+  /**
+   * Accesses the given <i>ByteBuffer</i> for write operations. The returned <i>WritableMemory</i> object has
+   * the same byte order, as the given <i>ByteBuffer</i>.
+   * @param byteBuffer the given <i>ByteBuffer</i>. It must be non-null, with capacity &ge; 0, and writable.
+   * @return a new <i>WritableMemory</i> for write operations on the given <i>ByteBuffer</i>.
+   */
+  static WritableMemory writableWrap(ByteBuffer byteBuffer) {
+    return writableWrap(byteBuffer, byteBuffer.order(), defaultMemReqSvr);
+  }
+
+  /**
+   * Accesses the given <i>ByteBuffer</i> for write operations. The returned <i>WritableMemory</i> object has
+   * the given byte order, ignoring the byte order of the given <i>ByteBuffer</i> for future writes and following reads.
+   * However, this does not change the byte order of data already in the <i>ByteBuffer</i>.
+   * @param byteBuffer the given <i>ByteBuffer</i>. It must be non-null, with capacity &ge; 0, and writable.
+   * @param byteOrder the byte order to be used. It must be non-null.
+   * @return a new <i>WritableMemory</i> for write operations on the given <i>ByteBuffer</i>.
+   */
+  static WritableMemory writableWrap(ByteBuffer byteBuffer, ByteOrder byteOrder) {
+    return writableWrap(byteBuffer, byteOrder, defaultMemReqSvr);
+  }
+
+  /**
+   * Accesses the given <i>ByteBuffer</i> for write operations. The returned <i>WritableMemory</i> object has
+   * the given byte order, ignoring the byte order of the given <i>ByteBuffer</i> for future reads and writes.
+   * However, this does not change the byte order of data already in the <i>ByteBuffer</i>.
+   * @param byteBuffer the given <i>ByteBuffer</i>. It must be non-null, with capacity &ge; 0, and writable.
+   * @param byteOrder the byte order to be used. It must be non-null.
+   * @param memReqSvr A user-specified <i>MemoryRequestServer</i>, which may be null.
+   * This is a callback mechanism for a user client to request a larger <i>WritableMemory</i>.
+   * @return a new <i>WritableMemory</i> for write operations on the given <i>ByteBuffer</i>.
+   */
+  static WritableMemory writableWrap(ByteBuffer byteBuffer, ByteOrder byteOrder, MemoryRequestServer memReqSvr) {
+    Objects.requireNonNull(byteBuffer, "byteBuffer must be non-null");
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null");
+    negativeCheck(byteBuffer.capacity(), "byteBuffer.capacity()");
+    if (byteBuffer.isReadOnly()) { throw new ReadOnlyException("byteBuffer must be writable."); }
+    return BaseWritableMemoryImpl.wrapByteBuffer(byteBuffer, false, byteOrder, memReqSvr);
+  }
+
+  //MAP
+  /**
+   * Maps the entire given file into native-ordered WritableMemory for write operations
+   * Calling this method is equivalent to calling
+   * {@link #writableMap(File, long, long, ByteOrder) writableMap(file, 0, file.length(), ByteOrder.nativeOrder())}.
+   * @param file the given file to map. It must be non-null, with length &ge; 0, and writable.
+   * @return WritableMapHandle for managing the mapped Memory.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static WritableMapHandle writableMap(File file) {
+    return writableMap(file, 0, file.length(), ByteOrder.nativeOrder());
+  }
+
+  /**
+   * Maps the specified portion of the given file into Memory for write operations.
+   *
+   * <p><b>Note:</b> Always qualify this method with the class name, e.g.,
+   * <i>WritableMemory.map(...)</i>.
+   * @param file the given file to map. It must be non-null, writable and length &ge; 0.
+   * @param fileOffsetBytes the position in the given file in bytes. It must not be negative.
+   * @param capacityBytes the size of the mapped Memory. It must be &ge; 0.
+   * @param byteOrder the byte order to be used for the given file. It must be non-null.
+   * @return WritableMapHandle for managing the mapped Memory.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static WritableMapHandle writableMap(File file, long fileOffsetBytes, long capacityBytes, ByteOrder byteOrder) {
+    Objects.requireNonNull(file, "file must be non-null.");
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null.");
+    if (!file.canWrite()) { throw new ReadOnlyException("file must be writable."); }
+    negativeCheck(file.length(), "file.length()");
+    negativeCheck(fileOffsetBytes, "fileOffsetBytes");
+    negativeCheck(capacityBytes, "capacityBytes");
+    return BaseWritableMemoryImpl.wrapMap(file, fileOffsetBytes, capacityBytes, false, byteOrder);
+  }
+
+  //ALLOCATE DIRECT
+  /**
+   * Allocates and provides access to capacityBytes directly in native (off-heap) memory.
+   * Native byte order is assumed.
+   * The allocated memory will be 8-byte aligned, but may not be page aligned.
+   *
+   * <p><b>NOTE:</b> Native/Direct memory acquired may have garbage in it.
+   * It is the responsibility of the using application to clear this memory, if required,
+   * and to call <i>close()</i> when done.</p>
+   *
+   * @param capacityBytes the size of the desired memory in bytes. It must be &gt; 0.
+   * @return WritableHandle for this off-heap resource.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static WritableHandle allocateDirect(long capacityBytes) {
+    return allocateDirect(capacityBytes, ByteOrder.nativeOrder(), defaultMemReqSvr);
+  }
+
+  /**
+   * Allocates and provides access to capacityBytes directly in native (off-heap) memory.
+   * The allocated memory will be 8-byte aligned, but may not be page aligned.
+   *
+   * <p><b>NOTE:</b> Native/Direct memory acquired may have garbage in it.
+   * It is the responsibility of the using application to clear this memory, if required,
+   * and to call <i>close()</i> when done.</p>
+   *
+   * @param capacityBytes the size of the desired memory in bytes. It must be &gt; 0.
+   * @param byteOrder the given byte order. It must be non-null.
+   * @param memReqSvr A user-specified MemoryRequestServer, which may be null.
+   * This is a callback mechanism for a user client of direct memory to request more memory.
+   * @return WritableHandle for this off-heap resource.
+   * Please read Javadocs for {@link Handle}.
+   */
+  static WritableHandle allocateDirect(long capacityBytes, ByteOrder byteOrder, MemoryRequestServer memReqSvr) {
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null");
+    zeroCheck(capacityBytes, "capacityBytes");
+    return BaseWritableMemoryImpl.wrapDirect(capacityBytes, byteOrder, memReqSvr);
+  }
+
+  //REGIONS
+  /**
+   * A writable region is a writable view of this object.
+   * This returns a new <i>WritableMemory</i> representing the defined writable region with the
+   * given offsetBytes and capacityBytes.
+   * <ul>
+   * <li>Returned object's origin = this objects' origin + <i>offsetBytes</i></li>
+   * <li>Returned object's capacity = <i>capacityBytes</i></li>
+   * </ul>
+   *
+   * @param offsetBytes the starting offset with respect to this object. It must be &ge; 0.
+   * @param capacityBytes the capacity of the returned object in bytes. It must be &gt; 0.
+   * @return a new <i>WritableMemory</i> representing the defined writable region.
+   */
+  default WritableMemory writableRegion(long offsetBytes, long capacityBytes) {
+    return writableRegion(offsetBytes, capacityBytes, getTypeByteOrder());
+  }
+
+  /**
+   * A writable region is a writable view of this object.
+   * This returns a new <i>WritableMemory</i> representing the defined writable region with the
+   * given offsetBytes, capacityBytes and byte order.
+   * <ul>
+   * <li>Returned object's origin = this objects' origin + <i>offsetBytes</i></li>
+   * <li>Returned object's capacity = <i>capacityBytes</i></li>
+   * <li>Returned object's byte order = <i>byteOrder</i></li>
+   * </ul>
+   *
+   * @param offsetBytes the starting offset with respect to this object. It must be &ge; 0.
+   * @param capacityBytes the capacity of the returned object in bytes. It must be &gt; 0.
+   * @param byteOrder the given byte order. It must be non-null.
+   * @return a new <i>WritableMemory</i> representing the defined writable region.
+   */
+  WritableMemory writableRegion(long offsetBytes, long capacityBytes, ByteOrder byteOrder);
+
+  //AS WRITABLE BUFFER
+  /**
+   * Returns a new <i>WritableBuffer</i> with a writable view of this object.
+   * <ul>
+   * <li>Returned object's origin = this object's origin</li>
+   * <li>Returned object's <i>start</i> = 0</li>
+   * <li>Returned object's <i>position</i> = 0</li>
+   * <li>Returned object's <i>end</i> = this object's capacity</li>
+   * <li>Returned object's <i>capacity</i> = this object's capacity</li>
+   * <li>Returned object's <i>start</i>, <i>position</i> and <i>end</i> are mutable</li>
+   * </ul>
+   * @return a new <i>WritableBuffer</i> with a view of this WritableMemory
+   */
+  default WritableBuffer asWritableBuffer() {
+    return asWritableBuffer(getTypeByteOrder());
+  }
+
+  /**
+   * Returns a new <i>WritableBuffer</i> with a writable view of this object
+   * with the given byte order.
+   * <ul>
+   * <li>Returned object's origin = this object's origin</li>
+   * <li>Returned object's <i>start</i> = 0</li>
+   * <li>Returned object's <i>position</i> = 0</li>
+   * <li>Returned object's <i>end</i> = this object's capacity</li>
+   * <li>Returned object's <i>capacity</i> = this object's capacity</li>
+   * <li>Returned object's <i>start</i>, <i>position</i> and <i>end</i> are mutable</li>
+   * </ul>
+   * @param byteOrder the given byte order
+   * @return a new <i>WritableBuffer</i> with a view of this WritableMemory
+   */
+  WritableBuffer asWritableBuffer(ByteOrder byteOrder);
+
+
+  //ALLOCATE HEAP VIA AUTOMATIC BYTE ARRAY
+  /**
+   * Creates on-heap WritableMemory with the given capacity and the native byte order.
+   * @param capacityBytes the given capacity in bytes. It must be &ge; 0.
+   * @return a new WritableMemory for write operations on a new byte array.
+   */
+  static WritableMemory allocate(int capacityBytes) {
+    return allocate(capacityBytes, ByteOrder.nativeOrder(), defaultMemReqSvr);
+  }
+
+  /**
+   * Creates on-heap WritableMemory with the given capacity and the given byte order.
+   * @param capacityBytes the given capacity in bytes. It must be &ge; 0.
+   * @param byteOrder the given byte order to allocate new Memory object with. It must be non-null.
+   * @return a new WritableMemory for write operations on a new byte array.
+   */
+  static WritableMemory allocate(int capacityBytes, ByteOrder byteOrder) {
+    return allocate(capacityBytes, byteOrder, defaultMemReqSvr);
+  }
+
+  /**
+   * Creates on-heap WritableMemory with the given capacity and the given byte order.
+   * @param capacityBytes the given capacity in bytes. It must be &ge; 0.
+   * @param byteOrder the given byte order to allocate new Memory object with. It must be non-null.
+   * @param memReqSvr A user-specified <i>MemoryRequestServer</i>, which may be null.
+   * This is a callback mechanism for a user client to request a larger <i>WritableMemory</i>.
+   * @return a new WritableMemory for write operations on a new byte array.
+   */
+  static WritableMemory allocate(int capacityBytes, ByteOrder byteOrder, MemoryRequestServer memReqSvr) {
+    final byte[] arr = new byte[capacityBytes];
+    negativeCheck(capacityBytes, "capacityBytes");
+    return writableWrap(arr, 0, capacityBytes, byteOrder, memReqSvr);
+  }
+
+
+  //ACCESS PRIMITIVE HEAP ARRAYS for WRITE
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   *
+   * <p><b>Note:</b> Always qualify this method with the class name, e.g.,
+   * <i>WritableMemory.wrap(...)</i>.
+   * @param array the given primitive array. It must be non-null, with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(byte[] array) {
+    return writableWrap(array, 0, array.length, ByteOrder.nativeOrder(), defaultMemReqSvr);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations with the given byte order.
+   *
+   * <p><b>Note:</b> Always qualify this method with the class name, e.g.,
+   * <i>WritableMemory.wrap(...)</i>.
+   * @param array the given primitive array. It must be non-null, with length &ge; 0
+   * @param byteOrder the byte order to be used. It must be non-null.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(byte[] array, ByteOrder byteOrder) {
+    return writableWrap(array, 0, array.length, byteOrder, defaultMemReqSvr);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations with the given byte order.
+   *
+   * <p><b>Note:</b> Always qualify this method with the class name, e.g.,
+   * <i>WritableMemory.wrap(...)</i>.
+   * @param array the given primitive array. It must be non-null.
+   * @param offsetBytes the byte offset into the given array. It must be &ge; 0.
+   * @param lengthBytes the number of bytes to include from the given array. It must be &ge; 0.
+   * @param byteOrder the byte order to be used. It must be non-null.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(byte[] array, int offsetBytes, int lengthBytes, ByteOrder byteOrder) {
+    return writableWrap(array, offsetBytes, lengthBytes, byteOrder, defaultMemReqSvr);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations with the given byte order. If the given
+   * lengthBytes is zero, backing storage, byte order and read-only status of the returned
+   * WritableMemory object are unspecified.
+   *
+   * <p><b>Note:</b> Always qualify this method with the class name, e.g.,
+   * <i>WritableMemory.wrap(...)</i>.
+   * @param array the given primitive array. It must be non-null.
+   * @param offsetBytes the byte offset into the given array. It must be &ge; 0.
+   * @param lengthBytes the number of bytes to include from the given array. It must be &ge; 0.
+   * @param byteOrder the byte order to be used. It must be non-null.
+   * @param memReqSvr A user-specified <i>MemoryRequestServer</i>, which may be null.
+   * This is a callback mechanism for a user client to request a larger <i>WritableMemory</i>.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(byte[] array, int offsetBytes, int lengthBytes, ByteOrder byteOrder,
+      MemoryRequestServer memReqSvr) {
+    Objects.requireNonNull(array, "array must be non-null");
+    Objects.requireNonNull(byteOrder, "byteOrder must be non-null");
+    negativeCheck(offsetBytes, "offsetBytes");
+    negativeCheck(lengthBytes, "lengthBytes");
+    UnsafeUtil.checkBounds(offsetBytes, lengthBytes, array.length);
+    return BaseWritableMemoryImpl.wrapHeapArray(array, offsetBytes, lengthBytes, false, byteOrder, memReqSvr);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(boolean[] array) {
+    Objects.requireNonNull(array, "array must be non-null");
+    final long lengthBytes = array.length << Prim.BOOLEAN.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(char[] array) {
+    Objects.requireNonNull(array, "array must be non-null");
+    final long lengthBytes = array.length << Prim.CHAR.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(short[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.SHORT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(int[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.INT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(long[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.LONG.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(float[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.FLOAT.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+
+  /**
+   * Wraps the given primitive array for write operations assuming native byte order.
+   * @param array the given primitive array. It must be non-null with length &ge; 0.
+   * @return a new WritableMemory for write operations on the given primitive array.
+   */
+  static WritableMemory writableWrap(double[] array) {
+    Objects.requireNonNull(array, "arr must be non-null");
+    final long lengthBytes = array.length << Prim.DOUBLE.shift();
+    return BaseWritableMemoryImpl.wrapHeapArray(array, 0L, lengthBytes, false, ByteOrder.nativeOrder(), null);
+  }
+  //END OF CONSTRUCTOR-TYPE METHODS
+
+  //PRIMITIVE putX() and putXArray()
+  /**
+   * Puts the boolean value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putBoolean(long offsetBytes, boolean value);
+
+  /**
+   * Puts the boolean array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetBooleans offset in array units
+   * @param lengthBooleans number of array units to transfer
+   */
+  void putBooleanArray(long offsetBytes, boolean[] srcArray, int srcOffsetBooleans, int lengthBooleans);
+
+  /**
+   * Puts the byte value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putByte(long offsetBytes, byte value);
+
+  /**
+   * Puts the byte array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetBytes offset in array units
+   * @param lengthBytes number of array units to transfer
+   */
+  void putByteArray(long offsetBytes, byte[] srcArray, int srcOffsetBytes, int lengthBytes);
+
+  /**
+   * Puts the char value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putChar(long offsetBytes, char value);
+
+  /**
+   * Puts the char array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetChars offset in array units
+   * @param lengthChars number of array units to transfer
+   */
+  void putCharArray(long offsetBytes, char[] srcArray, int srcOffsetChars, int lengthChars);
+
+  /**
+   * Encodes characters from the given CharSequence into UTF-8 bytes and puts them into this
+   * <i>WritableMemory</i> begining at the given offsetBytes.
+   * This is specifically designed to reduce the production of intermediate objects (garbage),
+   * thus significantly reducing pressure on the JVM Garbage Collector.
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param src The source CharSequence to be encoded and put into this WritableMemory. It is
+   * the responsibility of the caller to provide sufficient capacity in this
+   * <i>WritableMemory</i> for the encoded Utf8 bytes. Characters outside the ASCII range can
+   * require 2, 3 or 4 bytes per character to encode.
+   * @return the number of bytes encoded
+   */
+  long putCharsToUtf8(long offsetBytes, CharSequence src);
+
+  /**
+   * Puts the double value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putDouble(long offsetBytes, double value);
+
+  /**
+   * Puts the double array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetDoubles offset in array units
+   * @param lengthDoubles number of array units to transfer
+   */
+  void putDoubleArray(long offsetBytes, double[] srcArray, int srcOffsetDoubles, int lengthDoubles);
+
+  /**
+   * Puts the float value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putFloat(long offsetBytes, float value);
+
+  /**
+   * Puts the float array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetFloats offset in array units
+   * @param lengthFloats number of array units to transfer
+   */
+  void putFloatArray(long offsetBytes, float[] srcArray, int srcOffsetFloats, int lengthFloats);
+
+  /**
+   * Puts the int value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putInt(long offsetBytes, int value);
+
+  /**
+   * Puts the int array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetInts offset in array units
+   * @param lengthInts number of array units to transfer
+   */
+  void putIntArray(long offsetBytes, int[] srcArray, int srcOffsetInts, int lengthInts);
+
+  /**
+   * Puts the long value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putLong(long offsetBytes, long value);
+
+  /**
+   * Puts the long array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetLongs offset in array units
+   * @param lengthLongs number of array units to transfer
+   */
+  void putLongArray(long offsetBytes, long[] srcArray, int srcOffsetLongs, int lengthLongs);
+
+  /**
+   * Puts the short value at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param value the value to put
+   */
+  void putShort(long offsetBytes, short value);
+
+  /**
+   * Puts the short array at the given offset
+   * @param offsetBytes offset bytes relative to this <i>WritableMemory</i> start
+   * @param srcArray The source array.
+   * @param srcOffsetShorts offset in array units
+   * @param lengthShorts number of array units to transfer
+   */
+  void putShortArray(long offsetBytes, short[] srcArray, int srcOffsetShorts, int lengthShorts);
+
+  //Atomic Methods
+  /**
+   * Atomically adds the given value to the long located at offsetBytes.
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param delta the amount to add
+   * @return the the previous value
+   */
+  long getAndAddLong(long offsetBytes, long delta);
+
+  /**
+   * Atomically sets the current value at the memory location to the given updated value
+   * if and only if the current value {@code ==} the expected value.
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param expect the expected value
+   * @param update the new value
+   * @return {@code true} if successful. False return indicates that
+   * the current value at the memory location was not equal to the expected value.
+   */
+  boolean compareAndSwapLong(long offsetBytes, long expect, long update);
+
+  /**
+   * Atomically exchanges the given value with the current value located at offsetBytes.
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param newValue new value
+   * @return the previous value
+   */
+  long getAndSetLong(long offsetBytes, long newValue);
+
+  //OTHER WRITE METHODS
+  /**
+   * Returns the primitive backing array, otherwise null.
+   * @return the primitive backing array, otherwise null.
+   */
+  Object getArray();
+
+  /**
+   * Clears all bytes of this Memory to zero
+   */
+  void clear();
+
+  /**
+   * Clears a portion of this Memory to zero.
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param lengthBytes the length in bytes
+   */
+  void clear(long offsetBytes, long lengthBytes);
+
+  /**
+   * Clears the bits defined by the bitMask
+   * @param offsetBytes offset bytes relative to this Memory start.
+   * @param bitMask the bits set to one will be cleared
+   */
+  void clearBits(long offsetBytes, byte bitMask);
+
+  /**
+   * Fills all bytes of this Memory region to the given byte value.
+   * @param value the given byte value
+   */
+  void fill(byte value);
+
+  /**
+   * Fills a portion of this Memory region to the given byte value.
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param lengthBytes the length in bytes
+   * @param value the given byte value
+   */
+  void fill(long offsetBytes, long lengthBytes, byte value);
+
+  /**
+   * Sets the bits defined by the bitMask
+   * @param offsetBytes offset bytes relative to this Memory start
+   * @param bitMask the bits set to one will be set
+   */
+  void setBits(long offsetBytes, byte bitMask);
+
+
+  //OTHER WRITABLE API METHODS
+  /**
+   * WritableMemory enables this for ByteBuffer, Heap and Direct Memory backed resources.
+   * Map backed resources will always return null.
+   * Gets the MemoryRequestServer object, if set, for the above resources to request additional memory.
+   * The user must customize the actions of the MemoryRequestServer by
+   * implementing the MemoryRequestServer interface and set using one of these methods:
+   * <ul><li>{@link WritableMemory#allocateDirect(long, ByteOrder, MemoryRequestServer)}</li>
+   * <li>{@link WritableMemory#allocate(int, ByteOrder, MemoryRequestServer)}</li>
+   * <li>{@link WritableMemory#writableWrap(ByteBuffer, ByteOrder, MemoryRequestServer)}</li>
+   * </ul>
+   * Simple implementation examples include the DefaultMemoryRequestServer in the main tree, as well as
+   * the ExampleMemoryRequestServerTest and the use with ByteBuffer documented in the DruidIssue11544Test
+   * in the test tree.
+   * @return the MemoryRequestServer object or null.
+   */
+  MemoryRequestServer getMemoryRequestServer();
+
+}
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/BaseStateImpl.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/BaseStateImpl.java
new file mode 100644
index 0000000..bd6295e
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/BaseStateImpl.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.memory.internal;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.datasketches.memory.BaseState;
+import org.apache.datasketches.memory.MemoryRequestServer;
+import org.apache.datasketches.memory.ReadOnlyException;
+
+/**
+ * Keeps key configuration state for MemoryImpl and BufferImpl plus some common static variables
+ * and check methods.
+ *
+ * @author Lee Rhodes
+ */
+@SuppressWarnings("restriction")
+public abstract class BaseStateImpl implements BaseState {
+
+  //Monitoring
+  static final AtomicLong currentDirectMemoryAllocations_ = new AtomicLong();
+  static final AtomicLong currentDirectMemoryAllocated_ = new AtomicLong();
+  static final AtomicLong currentDirectMemoryMapAllocations_ = new AtomicLong();
+  static final AtomicLong currentDirectMemoryMapAllocated_ = new AtomicLong();
+
+  //class type IDs. Do not change the bit orders
+  //The first 3 bits are set dynamically
+  // 0000 0XXX
+  static final int READONLY = 1;
+  static final int REGION = 2;
+  static final int DUPLICATE = 4;
+
+  //The following 4 bits are set by the 16 leaf nodes
+  // 000X X000
+  static final int HEAP = 0;
+  static final int DIRECT = 1 << 3;
+  static final int MAP = 2 << 3;
+  static final int BYTEBUF = 3 << 3;
+
+  // 00X0 0000
+  static final int NATIVE = 0;
+  static final int NONNATIVE = 1 << 5;
+
+  // 0X00 0000
+  static final int MEMORY = 0;
+  static final int BUFFER = 1 << 6;
+
+  private final long capacityBytes_;
+
+  /**
+   * This becomes the base offset used by all Unsafe calls. It is cumulative in that in includes
+   * all offsets from regions, user-defined offsets when creating MemoryImpl, and the array object
+   * header offset when creating MemoryImpl from primitive arrays.
+   */
+  private final long cumBaseOffset_;
+
+  /**
+   *
+   * @param unsafeObj The primitive backing array. It may be null. Used by Unsafe calls.
+   * @param nativeBaseOffset The off-heap memory address including DirectByteBuffer split offsets.
+   * @param regionOffset This offset defines address zero of this object (usually a region)
+   * relative to address zero of the backing resource. It is used to compute cumBaseOffset.
+   * This will be loaded from heap ByteBuffers, which have a similar field used for slices.
+   * It is used by region() and writableRegion().
+   * This offset does not include the size of an object array header, if there is one.
+   * @param capacityBytes the capacity of this object. Used by all methods when checking bounds.
+   */
+  BaseStateImpl(final Object unsafeObj, final long nativeBaseOffset, final long regionOffset,
+      final long capacityBytes) {
+    capacityBytes_ = capacityBytes;
+    cumBaseOffset_ = regionOffset + (unsafeObj == null
+        ? nativeBaseOffset
+        : UnsafeUtil.getArrayBaseOffset(unsafeObj.getClass()));
+  }
+
+  //Byte Order Related
+
+  @Override
+  public final ByteOrder getTypeByteOrder() {
+    return isNonNativeType() ? Util.NON_NATIVE_BYTE_ORDER : ByteOrder.nativeOrder();
+  }
+
+  /**
+   * Returns true if the given byteOrder is the same as the native byte order.
+   * @param byteOrder the given byte order
+   * @return true if the given byteOrder is the same as the native byte order.
+   */
+  public static boolean isNativeByteOrder(final ByteOrder byteOrder) {
+    if (byteOrder == null) {
+      throw new IllegalArgumentException("ByteOrder parameter cannot be null.");
+    }
+    return ByteOrder.nativeOrder() == byteOrder;
+  }
+
+  @Override
+  public final boolean isByteOrderCompatible(final ByteOrder byteOrder) {
+    final ByteOrder typeBO = getTypeByteOrder();
+    return typeBO == ByteOrder.nativeOrder() && typeBO == byteOrder;
+  }
+
+  @Override
+  public final boolean equals(final Object that) {
+    if (this == that) { return true; }
+    return that instanceof BaseStateImpl
+      ? CompareAndCopy.equals(this, (BaseStateImpl) that)
+      : false;
+  }
+
+  @Override
+  public final boolean equalTo(final long thisOffsetBytes, final Object that,
+      final long thatOffsetBytes, final long lengthBytes) {
+    return that instanceof BaseStateImpl
+      ? CompareAndCopy.equals(this, thisOffsetBytes, (BaseStateImpl) that, thatOffsetBytes, lengthBytes)
+      : false;
+  }
+
+  //Overridden by ByteBuffer Leafs
+  @Override
+  public ByteBuffer getByteBuffer() {
+    return null;
+  }
+
+  @Override
+  public final long getCapacity() {
+    assertValid();
+    return capacityBytes_;
+  }
+
+  @Override
+  public final long getCumulativeOffset() {
+    assertValid();
+    return cumBaseOffset_;
+  }
+
+  @Override
+  public final long getCumulativeOffset(final long offsetBytes) {
+    assertValid();
+    return cumBaseOffset_ + offsetBytes;
+  }
+
+  //Documented in WritableMemory and WritableBuffer interfaces.
+  //Implemented in the Leaf nodes; Required here by toHex(...).
+  abstract MemoryRequestServer getMemoryRequestServer();
+
+  //Overridden by ByteBuffer, Direct and Map leafs
+  long getNativeBaseOffset() {
+    return 0;
+  }
+
+  @Override
+  public final long getRegionOffset() {
+    final Object unsafeObj = getUnsafeObject();
+    return unsafeObj == null
+        ? cumBaseOffset_ - getNativeBaseOffset()
+        : cumBaseOffset_ - UnsafeUtil.getArrayBaseOffset(unsafeObj.getClass());
+  }
+
+  @Override
+  public final long getRegionOffset(final long offsetBytes) {
+    return getRegionOffset() + offsetBytes;
+  }
+
+  //Overridden by all leafs
+  abstract int getTypeId();
+
+  //Overridden by Heap and ByteBuffer Leafs. Made public as getArray() in WritableMemoryImpl and
+  // WritableBufferImpl
+  Object getUnsafeObject() {
+    return null;
+  }
+
+  @Override
+  public final boolean hasArray() {
+    assertValid();
+    return getUnsafeObject() != null;
+  }
+
+  @Override
+  public final int hashCode() {
+    return (int) xxHash64(0, capacityBytes_, 0); //xxHash64() calls checkValid()
+  }
+
+  @Override
+  public final long xxHash64(final long offsetBytes, final long lengthBytes, final long seed) {
+    checkValid();
+    return XxHash64.hash(getUnsafeObject(), cumBaseOffset_ + offsetBytes, lengthBytes, seed);
+  }
+
+  @Override
+  public final long xxHash64(final long in, final long seed) {
+    return XxHash64.hash(in, seed);
+  }
+
+  @Override
+  public final boolean hasByteBuffer() {
+    assertValid();
+    return getByteBuffer() != null;
+  }
+
+  @Override
+  public final boolean isDirect() {
+    return getUnsafeObject() == null;
+  }
+
+  @Override
+  public final boolean isReadOnly() {
+    assertValid();
+    return isReadOnlyType();
+  }
+
+  @Override
+  public final boolean isSameResource(final Object that) {
+    checkValid();
+    if (that == null) { return false; }
+    final BaseStateImpl that1 = (BaseStateImpl) that;
+    that1.checkValid();
+    if (this == that1) { return true; }
+
+    return cumBaseOffset_ == that1.cumBaseOffset_
+            && capacityBytes_ == that1.capacityBytes_
+            && getUnsafeObject() == that1.getUnsafeObject()
+            && getByteBuffer() == that1.getByteBuffer();
+  }
+
+  //Overridden by Direct and Map leafs
+  @Override
+  public boolean isValid() {
+    return true;
+  }
+
+  //ASSERTS AND CHECKS
+  final void assertValid() {
+    assert isValid() : "MemoryImpl not valid.";
+  }
+
+  void checkValid() {
+    if (!isValid()) {
+      throw new IllegalStateException("MemoryImpl not valid.");
+    }
+  }
+
+  final void assertValidAndBoundsForRead(final long offsetBytes, final long lengthBytes) {
+    assertValid();
+    // capacityBytes_ is intentionally read directly instead of calling getCapacity()
+    // because the later can make JVM to not inline the assert code path (and entirely remove it)
+    // even though it does nothing in production code path.
+    assertBounds(offsetBytes, lengthBytes, capacityBytes_);
+  }
+
+  final void assertValidAndBoundsForWrite(final long offsetBytes, final long lengthBytes) {
+    assertValid();
+    // capacityBytes_ is intentionally read directly instead of calling getCapacity()
+    // because the later can make JVM to not inline the assert code path (and entirely remove it)
+    // even though it does nothing in production code path.
+    assertBounds(offsetBytes, lengthBytes, capacityBytes_);
+    assert !isReadOnly() : "MemoryImpl is read-only.";
+  }
+
+  @Override
+  public final void checkValidAndBounds(final long offsetBytes, final long lengthBytes) {
+    checkValid();
+    //read capacityBytes_ directly to eliminate extra checkValid() call
+    checkBounds(offsetBytes, lengthBytes, capacityBytes_);
+  }
+
+  final void checkValidAndBoundsForWrite(final long offsetBytes, final long lengthBytes) {
+    checkValid();
+    //read capacityBytes_ directly to eliminate extra checkValid() call
+    checkBounds(offsetBytes, lengthBytes, capacityBytes_);
+    if (isReadOnly()) {
+      throw new ReadOnlyException("MemoryImpl is read-only.");
+    }
+  }
+
+  //TYPE ID Management
+  final boolean isReadOnlyType() {
+    return (getTypeId() & READONLY) > 0;
+  }
+
+  final static byte setReadOnlyType(final byte type, final boolean readOnly) {
+    return (byte)((type & ~1) | (readOnly ? READONLY : 0));
+  }
+
+  final boolean isRegionType() {
+    return (getTypeId() & REGION) > 0;
+  }
+
+  final boolean isDuplicateType() {
+    return (getTypeId() & DUPLICATE) > 0;
+  }
+
+  //The following are set by the leaf nodes
+  final boolean isBufferType() {
+    return (getTypeId() & BUFFER) > 0;
+  }
+
+  final boolean isNonNativeType() {
+    return (getTypeId() & NONNATIVE) > 0;
+  }
+
+  final boolean isHeapType() {
+    return (getTypeId() >>> 3 & 3) == 0;
+  }
+
+  final boolean isDirectType() {
+    return (getTypeId() >>> 3 & 3) == 1;
+  }
+
+  final boolean isMapType() {
+    return (getTypeId() >>> 3 & 3) == 2;
+  }
+
+  final boolean isBBType() {
+    return (getTypeId() >>> 3 & 3) == 3;
+  }
+
+
+  //TO STRING
+  /**
+   * Decodes the resource type. This is primarily for debugging.
+   * @param typeId the given typeId
+   * @return a human readable string.
+   */
+  public static final String typeDecode(final int typeId) {
+    final StringBuilder sb = new StringBuilder();
+    final int group1 = typeId & 0x7;
+    switch (group1) {
+      case 1 : sb.append("ReadOnly, "); break;
+      case 2 : sb.append("Region, "); break;
+      case 3 : sb.append("ReadOnly Region, "); break;
+      case 4 : sb.append("Duplicate, "); break;
+      case 5 : sb.append("ReadOnly Duplicate, "); break;
+      case 6 : sb.append("Region Duplicate, "); break;
+      case 7 : sb.append("ReadOnly Region Duplicate, "); break;
+      default: break;
+    }
+    final int group2 = (typeId >>> 3) & 0x3;
+    switch (group2) {
+      case 0 : sb.append("Heap, "); break;
+      case 1 : sb.append("Direct, "); break;
+      case 2 : sb.append("Map, "); break;
+      case 3 : sb.append("ByteBuffer, "); break;
+      default: break;
+    }
+    final int group3 = (typeId >>> 5) & 0x1;
+    switch (group3) {
+      case 0 : sb.append("Native, "); break;
+      case 1 : sb.append("NonNative, "); break;
+      default: break;
+    }
+    final int group4 = (typeId >>> 6) & 0x1;
+    switch (group4) {
+      case 0 : sb.append("Memory"); break;
+      case 1 : sb.append("Buffer"); break;
+      default: break;
+    }
+    return sb.toString();
+  }
+
+  @Override
+  public final String toHexString(final String header, final long offsetBytes,
+      final int lengthBytes) {
+    checkValid();
+    final String klass = this.getClass().getSimpleName();
+    final String s1 = String.format("(..., %d, %d)", offsetBytes, lengthBytes);
+    final long hcode = hashCode() & 0XFFFFFFFFL;
+    final String call = ".toHexString" + s1 + ", hashCode: " + hcode;
+    final StringBuilder sb = new StringBuilder();
+    sb.append("### ").append(klass).append(" SUMMARY ###").append(LS);
+    sb.append("Header Comment      : ").append(header).append(LS);
+    sb.append("Call Parameters     : ").append(call);
+    return toHex(this, sb.toString(), offsetBytes, lengthBytes);
+  }
+
+  /**
+   * Returns a formatted hex string of an area of this object.
+   * Used primarily for testing.
+   * @param state the BaseStateImpl
+   * @param preamble a descriptive header
+   * @param offsetBytes offset bytes relative to the MemoryImpl start
+   * @param lengthBytes number of bytes to convert to a hex string
+   * @return a formatted hex string in a human readable array
+   */
+  static final String toHex(final BaseStateImpl state, final String preamble, final long offsetBytes,
+      final int lengthBytes) {
+    final long capacity = state.getCapacity();
+    UnsafeUtil.checkBounds(offsetBytes, lengthBytes, capacity);
+    final StringBuilder sb = new StringBuilder();
+    final Object uObj = state.getUnsafeObject();
+    final String uObjStr;
+    final long uObjHeader;
+    if (uObj == null) {
+      uObjStr = "null";
+      uObjHeader = 0;
+    } else {
+      uObjStr =  uObj.getClass().getSimpleName() + ", " + (uObj.hashCode() & 0XFFFFFFFFL);
+      uObjHeader = UnsafeUtil.getArrayBaseOffset(uObj.getClass());
+    }
+    final ByteBuffer bb = state.getByteBuffer();
+    final String bbStr = bb == null ? "null"
+            : bb.getClass().getSimpleName() + ", " + (bb.hashCode() & 0XFFFFFFFFL);
+    final MemoryRequestServer memReqSvr = state.getMemoryRequestServer();
+    final String memReqStr = memReqSvr != null
+        ? memReqSvr.getClass().getSimpleName() + ", " + (memReqSvr.hashCode() & 0XFFFFFFFFL)
+        : "null";
+    final long cumBaseOffset = state.getCumulativeOffset();
+    sb.append(preamble).append(LS);
+    sb.append("UnsafeObj, hashCode : ").append(uObjStr).append(LS);
+    sb.append("UnsafeObjHeader     : ").append(uObjHeader).append(LS);
+    sb.append("ByteBuf, hashCode   : ").append(bbStr).append(LS);
+    sb.append("RegionOffset        : ").append(state.getRegionOffset()).append(LS);
+    sb.append("Capacity            : ").append(capacity).append(LS);
+    sb.append("CumBaseOffset       : ").append(cumBaseOffset).append(LS);
+    sb.append("MemReq, hashCode    : ").append(memReqStr).append(LS);
+    sb.append("Valid               : ").append(state.isValid()).append(LS);
+    sb.append("Read Only           : ").append(state.isReadOnly()).append(LS);
+    sb.append("Type Byte Order     : ").append(state.getTypeByteOrder().toString()).append(LS);
+    sb.append("Native Byte Order   : ").append(ByteOrder.nativeOrder().toString()).append(LS);
+    sb.append("JDK Runtime Version : ").append(UnsafeUtil.JDK).append(LS);
+    //Data detail
+    sb.append("Data, littleEndian  :  0  1  2  3  4  5  6  7");
+
+    for (long i = 0; i < lengthBytes; i++) {
+      final int b = unsafe.getByte(uObj, cumBaseOffset + offsetBytes + i) & 0XFF;
+      if (i % 8 == 0) { //row header
+        sb.append(String.format("%n%20s: ", offsetBytes + i));
+      }
+      sb.append(String.format("%02x ", b));
+    }
+    sb.append(LS);
+
+    return sb.toString();
+  }
+
+  //MONITORING
+
+  /**
+   * Gets the current number of active direct memory allocations.
+   * @return the current number of active direct memory allocations.
+   */
+  public static final long getCurrentDirectMemoryAllocations() {
+    return BaseStateImpl.currentDirectMemoryAllocations_.get();
+  }
+
+  /**
+   * Gets the current size of active direct memory allocated.
+   * @return the current size of active direct memory allocated.
+   */
+  public static final long getCurrentDirectMemoryAllocated() {
+    return BaseStateImpl.currentDirectMemoryAllocated_.get();
+  }
+
+  /**
+   * Gets the current number of active direct memory map allocations.
+   * @return the current number of active direct memory map allocations.
+   */
+  public static final long getCurrentDirectMemoryMapAllocations() {
+    return BaseStateImpl.currentDirectMemoryMapAllocations_.get();
+  }
+
+  /**
+   * Gets the current size of active direct memory map allocated.
+   * @return the current size of active direct memory map allocated.
+   */
+  public static final long getCurrentDirectMemoryMapAllocated() {
+    return BaseStateImpl.currentDirectMemoryMapAllocated_.get();
+  }
+
+  //REACHABILITY FENCE
+  static void reachabilityFence(@SuppressWarnings("unused") final Object obj) { }
+
+}
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/CompareAndCopy.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/CompareAndCopy.java
new file mode 100644
index 0000000..cb5b9b0
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/CompareAndCopy.java
@@ -0,0 +1,522 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.memory.internal;
+
+import static org.apache.datasketches.memory.internal.UnsafeUtil.CHAR_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.DOUBLE_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.FLOAT_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.INT_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.LONG_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.SHORT_SHIFT;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.checkBounds;
+import static org.apache.datasketches.memory.internal.UnsafeUtil.unsafe;
+import static org.apache.datasketches.memory.internal.Util.UNSAFE_COPY_THRESHOLD_BYTES;
+
+/**
+ * @author Lee Rhodes
+ */
+@SuppressWarnings("restriction")
+final class CompareAndCopy {
+
+  private CompareAndCopy() { }
+
+  static int compare(
+      final BaseStateImpl state1, final long offsetBytes1, final long lengthBytes1,
+      final BaseStateImpl state2, final long offsetBytes2, final long lengthBytes2) {
+    state1.checkValid();
+    checkBounds(offsetBytes1, lengthBytes1, state1.getCapacity());
+    state2.checkValid();
+    checkBounds(offsetBytes2, lengthBytes2, state2.getCapacity());
+    final long cumOff1 = state1.getCumulativeOffset(offsetBytes1);
+    final long cumOff2 = state2.getCumulativeOffset(offsetBytes2);
+    final Object arr1 = state1.getUnsafeObject();
+    final Object arr2 = state2.getUnsafeObject();
+    if ((arr1 != arr2) || (cumOff1 != cumOff2)) {
+      final long lenBytes = Math.min(lengthBytes1, lengthBytes2);
+      for (long i = 0; i < lenBytes; i++) {
+        final int byte1 = unsafe.getByte(arr1, cumOff1 + i);
+        final int byte2 = unsafe.getByte(arr2, cumOff2 + i);
+        if (byte1 < byte2) { return -1; }
+        if (byte1 > byte2) { return  1; }
+      }
+    }
+    return Long.compare(lengthBytes1, lengthBytes2);
+  }
+
+  static boolean equals(final BaseStateImpl state1, final BaseStateImpl state2) {
+    final long cap1 = state1.getCapacity();
+    final long cap2 = state2.getCapacity();
+    return (cap1 == cap2) && equals(state1, 0, state2, 0, cap1);
+  }
+
+  //Developer notes: this is subtlely different from (campare == 0) in that this has an early
+  // stop if the arrays and offsets are the same as there is only one length.  Also this can take
+  // advantage of chunking with longs, while compare cannot.
+  static boolean equals(
+      final BaseStateImpl state1, final long offsetBytes1,
+      final BaseStateImpl state2, final long offsetBytes2, long lengthBytes) {
+    state1.checkValid();
+    checkBounds(offsetBytes1, lengthBytes, state1.getCapacity());
+    state2.checkValid();
+    checkBounds(offsetBytes2, lengthBytes, state2.getCapacity());
+    long cumOff1 = state1.getCumulativeOffset(offsetBytes1);
+    long cumOff2 = state2.getCumulativeOffset(offsetBytes2);
+    final Object arr1 = state1.getUnsafeObject(); //could be null
+    final Object arr2 = state2.getUnsafeObject(); //could be null
+    if ((arr1 == arr2) && (cumOff1 == cumOff2)) { return true; }
+
+    while (lengthBytes >= Long.BYTES) {
+      final int chunk = (int) Math.min(lengthBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      // int-counted loop to avoid safepoint polls (otherwise why we chunk by
+      // UNSAFE_COPY_MEMORY_THRESHOLD)
+      int i = 0;
+      for (; i <= (chunk - Long.BYTES); i += Long.BYTES) {
+        final long v1 = unsafe.getLong(arr1, cumOff1 + i);
+        final long v2 = unsafe.getLong(arr2, cumOff2 + i);
+        if (v1 != v2) { return false; }
+      }
+      lengthBytes -= i;
+      cumOff1 += i;
+      cumOff2 += i;
+    }
+    //check the remainder bytes, if any
+    return (lengthBytes == 0) || equalsByBytes(arr1, cumOff1, arr2, cumOff2, (int) lengthBytes);
+  }
+
+  //use only for short runs
+  private static boolean equalsByBytes(final Object arr1, final long cumOff1, final Object arr2,
+      final long cumOff2, final int lenBytes) {
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lenBytes; i++) {
+      final int v1 = unsafe.getByte(arr1, cumOff1 + i);
+      final int v2 = unsafe.getByte(arr2, cumOff2 + i);
+      if (v1 != v2) { return false; }
+    }
+    return true;
+  }
+
+  static void copy(final BaseStateImpl srcState, final long srcOffsetBytes,
+      final BaseStateImpl dstState, final long dstOffsetBytes, final long lengthBytes) {
+    srcState.checkValid();
+    checkBounds(srcOffsetBytes, lengthBytes, srcState.getCapacity());
+    dstState.checkValid();
+    checkBounds(dstOffsetBytes, lengthBytes, dstState.getCapacity());
+    final long srcAdd = srcState.getCumulativeOffset(srcOffsetBytes);
+    final long dstAdd = dstState.getCumulativeOffset(dstOffsetBytes);
+    copyMemory(srcState.getUnsafeObject(), srcAdd, dstState.getUnsafeObject(), dstAdd,
+        lengthBytes);
+  }
+
+  //Used by all of the get/put array methods in BufferImpl and MemoryImpl classes
+  static final void copyMemoryCheckingDifferentObject(final Object srcUnsafeObj,
+      final long srcAdd, final Object dstUnsafeObj, final long dstAdd, final long lengthBytes) {
+    if (srcUnsafeObj != dstUnsafeObj) {
+      copyNonOverlappingMemoryWithChunking(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd,
+          lengthBytes);
+    } else {
+      throw new IllegalArgumentException("Not expecting to copy to/from array which is the "
+          + "underlying object of the memory at the same time");
+    }
+  }
+
+  //only valid and bounds checks have been performed at this point
+  private static void copyMemory(final Object srcUnsafeObj, final long srcAdd,
+      final Object dstUnsafeObj, final long dstAdd, final long lengthBytes) {
+    if (srcUnsafeObj != dstUnsafeObj) {
+      //either srcArray != dstArray OR one of them is off-heap
+      copyNonOverlappingMemoryWithChunking(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd,
+          lengthBytes);
+    } else { //either srcArray == dstArray OR both src and dst are off-heap
+      copyMemoryOverlapAddressCheck(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd, lengthBytes);
+    }
+  }
+
+  /**
+   * At this point either srcArray == dstArray OR both src and dst are off-heap.
+   * Performs overlapping address check. If addresses do not overlap, proceed to
+   * {@link #copyNonOverlappingMemoryWithChunking(Object, long, Object, long, long)}; otherwise
+   * fall back on <i>Unsafe.copyMemory(...)</i> tolerating potentially long
+   * Time to Safe Point pauses.
+   * If srcAdd == dstAdd an exception will be thrown.
+   * @param srcUnsafeObj The source array object, it may be null.
+   * @param srcAdd The cumulative source offset
+   * @param dstUnsafeObj The destination array object, it may be null
+   * @param dstAdd The cumulative destination offset
+   * @param lengthBytes The length to be copied in bytes
+   */
+  private static void copyMemoryOverlapAddressCheck(final Object srcUnsafeObj, final long srcAdd,
+      final Object dstUnsafeObj, final long dstAdd, final long lengthBytes) {
+    if (((srcAdd + lengthBytes) <= dstAdd) || ((dstAdd + lengthBytes) <= srcAdd)) {
+      copyNonOverlappingMemoryWithChunking(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd,
+          lengthBytes);
+      return;
+    }
+    if (srcAdd == dstAdd) {
+      throw new IllegalArgumentException(
+          "Attempt to copy a block of memory exactly in-place, should be a bug");
+    }
+    // If regions do overlap, fall back to unsafe.copyMemory, tolerating potentially long
+    // Time to Safe Point pauses.
+    unsafe.copyMemory(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd, lengthBytes);
+  }
+
+  /**
+   * This copies only non-overlapping memory in chunks to avoid safepoint delays.
+   * Java 9 may not require the chunking.
+   * @param srcUnsafeObj The source array object, it may be null.
+   * @param srcAdd The cumulative source offset
+   * @param dstUnsafeObj The destination array object, it may be null
+   * @param dstAdd The cumulative destination offset
+   * @param lengthBytes The length to be copied in bytes
+   * @see #UNSAFE_COPY_THRESHOLD_BYTES
+   */
+  private static void copyNonOverlappingMemoryWithChunking(final Object srcUnsafeObj,
+      long srcAdd, final Object dstUnsafeObj, long dstAdd, long lengthBytes) {
+    while (lengthBytes > 0) {
+      final long chunk = Math.min(lengthBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      unsafe.copyMemory(srcUnsafeObj, srcAdd, dstUnsafeObj, dstAdd, chunk);
+      lengthBytes -= chunk;
+      srcAdd += chunk;
+      dstAdd += chunk;
+    }
+  }
+
+  static void getNonNativeChars(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final char[] dstArray, int dstOffsetChars,
+      int lengthChars) {
+    checkBounds(dstOffsetChars, lengthChars, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkChars = (int) (chunkBytes >> CHAR_SHIFT);
+      getCharArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetChars, chunkChars);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetChars += chunkChars;
+      copyBytes -= chunkBytes;
+      lengthChars -= chunkChars;
+    }
+    getCharArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetChars, lengthChars);
+  }
+
+  private static void getCharArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final char[] dstArray, final int dstOffsetChars, final int lengthChars) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthChars; i++) {
+      dstArray[dstOffsetChars + i] = Character.reverseBytes(
+          unsafe.getChar(unsafeObj, cumOffsetBytes + (((long) i) << CHAR_SHIFT)));
+    }
+  }
+
+  static void getNonNativeDoubles(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final double[] dstArray, int dstOffsetDoubles,
+      int lengthDoubles) {
+    checkBounds(dstOffsetDoubles, lengthDoubles, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkDoubles = (int) (chunkBytes >> DOUBLE_SHIFT);
+      getDoubleArrayChunk(unsafeObj, cumOffsetBytes,
+          dstArray, dstOffsetDoubles, chunkDoubles);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetDoubles += chunkDoubles;
+      copyBytes -= chunkBytes;
+      lengthDoubles -= chunkDoubles;
+    }
+    getDoubleArrayChunk(unsafeObj, cumOffsetBytes,
+        dstArray, dstOffsetDoubles, lengthDoubles);
+  }
+
+  private static void getDoubleArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final double[] dstArray, final int dstOffsetDoubles, final int lengthDoubles) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthDoubles; i++) {
+      dstArray[dstOffsetDoubles + i] = Double.longBitsToDouble(Long.reverseBytes(
+          unsafe.getLong(unsafeObj, cumOffsetBytes + (((long) i) << DOUBLE_SHIFT))));
+    }
+  }
+
+  static void getNonNativeFloats(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final float[] dstArray, int dstOffsetFloats,
+      int lengthFloats) {
+    checkBounds(dstOffsetFloats, lengthFloats, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkFloats = (int) (chunkBytes >> FLOAT_SHIFT);
+      getFloatArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetFloats, chunkFloats);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetFloats += chunkFloats;
+      copyBytes -= chunkBytes;
+      lengthFloats -= chunkFloats;
+    }
+    getFloatArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetFloats, lengthFloats);
+  }
+
+  private static void getFloatArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final float[] dstArray, final int dstOffsetFloats, final int lengthFloats) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthFloats; i++) {
+      dstArray[dstOffsetFloats + i] = Float.intBitsToFloat(Integer.reverseBytes(
+          unsafe.getInt(unsafeObj, cumOffsetBytes + (((long) i) << FLOAT_SHIFT))));
+    }
+  }
+
+  static void getNonNativeInts(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final int[] dstArray, int dstOffsetInts,
+      int lengthInts) {
+    checkBounds(dstOffsetInts, lengthInts, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkInts = (int) (chunkBytes >> INT_SHIFT);
+      getIntArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetInts, chunkInts);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetInts += chunkInts;
+      copyBytes -= chunkBytes;
+      lengthInts -= chunkInts;
+    }
+    getIntArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetInts, lengthInts);
+  }
+
+  private static void getIntArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final int[] dstArray, final int dstOffsetInts, final int lengthInts) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthInts; i++) {
+      dstArray[dstOffsetInts + i] = Integer.reverseBytes(
+          unsafe.getInt(unsafeObj, cumOffsetBytes + (((long) i) << INT_SHIFT)));
+    }
+  }
+
+  static void getNonNativeLongs(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final long[] dstArray, int dstOffsetLongs,
+      int lengthLongs) {
+    checkBounds(dstOffsetLongs, lengthLongs, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkLongs = (int) (chunkBytes >> LONG_SHIFT);
+      getLongArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetLongs, chunkLongs);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetLongs += chunkLongs;
+      copyBytes -= chunkBytes;
+      lengthLongs -= chunkLongs;
+    }
+    getLongArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetLongs, lengthLongs);
+  }
+
+  private static void getLongArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final long[] dstArray, final int dstOffsetLongs, final int lengthLongs) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthLongs; i++) {
+      dstArray[dstOffsetLongs + i] = Long.reverseBytes(
+          unsafe.getLong(unsafeObj, cumOffsetBytes + (((long) i) << LONG_SHIFT)));
+    }
+  }
+
+  static void getNonNativeShorts(final Object unsafeObj, long cumOffsetBytes,
+      long copyBytes, final short[] dstArray, int dstOffsetShorts,
+      int lengthShorts) {
+    checkBounds(dstOffsetShorts, lengthShorts, dstArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkShorts = (int) (chunkBytes >> SHORT_SHIFT);
+      getShortArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetShorts, chunkShorts);
+      cumOffsetBytes += chunkBytes;
+      dstOffsetShorts += chunkShorts;
+      copyBytes -= chunkBytes;
+      lengthShorts -= chunkShorts;
+    }
+    getShortArrayChunk(unsafeObj, cumOffsetBytes, dstArray, dstOffsetShorts, lengthShorts);
+  }
+
+  private static void getShortArrayChunk(final Object unsafeObj, final long cumOffsetBytes,
+      final short[] dstArray, final int dstOffsetShorts, final int lengthShorts) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthShorts; i++) {
+      dstArray[dstOffsetShorts + i] = Short.reverseBytes(
+          unsafe.getShort(unsafeObj, cumOffsetBytes + (((long) i) << SHORT_SHIFT)));
+    }
+  }
+
+  static void putNonNativeChars(final char[] srcArray, int srcOffsetChars, int lengthChars,
+      long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetChars, lengthChars, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkChars = (int) (chunkBytes >> CHAR_SHIFT);
+      putCharArrayChunk(srcArray, srcOffsetChars, chunkChars, unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetChars += chunkChars;
+      copyBytes -= chunkBytes;
+      lengthChars -= chunkChars;
+    }
+    putCharArrayChunk(srcArray, srcOffsetChars, lengthChars, unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putCharArrayChunk(final char[] srcArray, final int srcOffsetChars,
+      final int lengthChars, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthChars; i++) {
+      unsafe.putChar(unsafeObj, cumOffsetBytes + (((long) i) << CHAR_SHIFT),
+          Character.reverseBytes(srcArray[srcOffsetChars + i]));
+    }
+  }
+
+  static void putNonNativeDoubles(final double[] srcArray, int srcOffsetDoubles,
+      int lengthDoubles, long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetDoubles, lengthDoubles, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkDoubles = (int) (chunkBytes >> DOUBLE_SHIFT);
+      putDoubleArrayChunk(srcArray, srcOffsetDoubles, chunkDoubles,
+          unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetDoubles += chunkDoubles;
+      copyBytes -= chunkBytes;
+      lengthDoubles -= chunkDoubles;
+    }
+    putDoubleArrayChunk(srcArray, srcOffsetDoubles, lengthDoubles,
+        unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putDoubleArrayChunk(final double[] srcArray, final int srcOffsetDoubles,
+      final int lengthDoubles, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthDoubles; i++) {
+      unsafe.putLong(unsafeObj, cumOffsetBytes + (((long) i) << DOUBLE_SHIFT),
+          Long.reverseBytes(Double.doubleToRawLongBits(srcArray[srcOffsetDoubles + i])));
+    }
+  }
+
+  static void putNonNativeFloats(final float[] srcArray, int srcOffsetFloats,
+      int lengthFloats, long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetFloats, lengthFloats, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkFloats = (int) (chunkBytes >> FLOAT_SHIFT);
+      putFloatArrayChunk(srcArray, srcOffsetFloats, chunkFloats, unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetFloats += chunkFloats;
+      copyBytes -= chunkBytes;
+      lengthFloats -= chunkFloats;
+    }
+    putFloatArrayChunk(srcArray, srcOffsetFloats, lengthFloats, unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putFloatArrayChunk(final float[] srcArray, final int srcOffsetFloats,
+      final int lengthFloats, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthFloats; i++) {
+      unsafe.putInt(unsafeObj, cumOffsetBytes + (((long) i) << FLOAT_SHIFT),
+          Integer.reverseBytes(Float.floatToRawIntBits(srcArray[srcOffsetFloats + i])));
+    }
+  }
+
+  static void putNonNativeInts(final int[] srcArray, int srcOffsetInts, int lengthInts,
+      long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetInts, lengthInts, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkInts = (int) (chunkBytes >> INT_SHIFT);
+      putIntArrayChunk(srcArray, srcOffsetInts, chunkInts, unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetInts += chunkInts;
+      copyBytes -= chunkBytes;
+      lengthInts -= chunkInts;
+    }
+    putIntArrayChunk(srcArray, srcOffsetInts, lengthInts, unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putIntArrayChunk(final int[] srcArray, final int srcOffsetInts,
+      final int lengthInts, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthInts; i++) {
+      unsafe.putInt(unsafeObj, cumOffsetBytes + (((long) i) << INT_SHIFT),
+          Integer.reverseBytes(srcArray[srcOffsetInts + i]));
+    }
+  }
+
+  static void putNonNativeLongs(final long[] srcArray, int srcOffsetLongs, int lengthLongs,
+      long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetLongs, lengthLongs, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkLongs = (int) (chunkBytes >> LONG_SHIFT);
+      putLongArrayChunk(srcArray, srcOffsetLongs, chunkLongs, unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetLongs += chunkLongs;
+      copyBytes -= chunkBytes;
+      lengthLongs -= chunkLongs;
+    }
+    putLongArrayChunk(srcArray, srcOffsetLongs, lengthLongs, unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putLongArrayChunk(final long[] srcArray, final int srcOffsetLongs,
+      final int lengthLongs, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthLongs; i++) {
+      unsafe.putLong(unsafeObj, cumOffsetBytes + (((long) i) << LONG_SHIFT),
+          Long.reverseBytes(srcArray[srcOffsetLongs + i]));
+    }
+  }
+
+  static void putNonNativeShorts(final short[] srcArray, int srcOffsetShorts,
+      int lengthShorts, long copyBytes, final Object unsafeObj, long cumOffsetBytes) {
+    checkBounds(srcOffsetShorts, lengthShorts, srcArray.length);
+    while (copyBytes > UNSAFE_COPY_THRESHOLD_BYTES) {
+      final long chunkBytes = Math.min(copyBytes, UNSAFE_COPY_THRESHOLD_BYTES);
+      final int chunkShorts = (int) (chunkBytes >> SHORT_SHIFT);
+      putShortArrayChunk(srcArray, srcOffsetShorts, chunkShorts, unsafeObj, cumOffsetBytes);
+      cumOffsetBytes += chunkBytes;
+      srcOffsetShorts += chunkShorts;
+      copyBytes -= chunkBytes;
+      lengthShorts -= chunkShorts;
+    }
+    putShortArrayChunk(srcArray, srcOffsetShorts, lengthShorts, unsafeObj, cumOffsetBytes);
+  }
+
+  private static void putShortArrayChunk(final short[] srcArray, final int srcOffsetShorts,
+      final int lengthShorts, final Object unsafeObj, final long cumOffsetBytes) {
+    // JDK 9 adds native intrinsics for such bulk non-native ordered primitive memory copy.
+    // TODO-JDK9 use them when the library adds support for JDK 9
+    // int-counted loop to avoid safepoint polls
+    for (int i = 0; i < lengthShorts; i++) {
+      unsafe.putShort(unsafeObj, cumOffsetBytes + (((long) i) << SHORT_SHIFT),
+          Short.reverseBytes(srcArray[srcOffsetShorts + i]));
+    }
+  }
+}
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/UnsafeUtil.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/UnsafeUtil.java
new file mode 100644
index 0000000..67c1975
--- /dev/null
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/UnsafeUtil.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.memory.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import sun.misc.Unsafe;
+
+/**
+ * Provides access to the sun.misc.Unsafe class and its key static fields.
+ *
+ * @author Lee Rhodes
+ */
+@SuppressWarnings({"restriction", "javadoc"})
+public final class UnsafeUtil {
+  public static final Unsafe unsafe;
+  public static final String JDK; //must be at least "1.8"
+  public static final int JDK_MAJOR; //8, 9, 10, 11, 12, etc
+
+  //not an indicator of whether compressed references are used.
+  public static final int ADDRESS_SIZE;
+  public static final int PAGE_SIZE;
+
+  //For 64-bit JVMs: these offsets vary depending on coop: 16 for JVM <= 32GB; 24 for JVM > 32GB.
+  // Making this constant long-typed, rather than int, to exclude possibility of accidental overflow
+  // in expressions like arrayLength * ARRAY_BYTE_BASE_OFFSET, where arrayLength is int-typed.
+  // The same consideration for constants below: ARRAY_*_INDEX_SCALE, ARRAY_*_INDEX_SHIFT.
+  public static final long ARRAY_BOOLEAN_BASE_OFFSET;
+  public static final long ARRAY_BYTE_BASE_OFFSET;
+  public static final long ARRAY_SHORT_BASE_OFFSET;
+  public static final long ARRAY_CHAR_BASE_OFFSET;
+  public static final long ARRAY_INT_BASE_OFFSET;
+  public static final long ARRAY_LONG_BASE_OFFSET;
+  public static final long ARRAY_FLOAT_BASE_OFFSET;
+  public static final long ARRAY_DOUBLE_BASE_OFFSET;
+  public static final long ARRAY_OBJECT_BASE_OFFSET;
+
+  //@formatter:off
+
+  // Setting those values directly instead of using unsafe.arrayIndexScale(), because it may be
+  // beneficial for runtime execution, those values are backed into generated machine code as
+  // constants. E. g. see https://shipilev.net/jvm-anatomy-park/14-constant-variables/
+  public static final int ARRAY_BOOLEAN_INDEX_SCALE = 1;
+  public static final int ARRAY_BYTE_INDEX_SCALE    = 1;
+  public static final long ARRAY_SHORT_INDEX_SCALE  = 2;
+  public static final long ARRAY_CHAR_INDEX_SCALE   = 2;
+  public static final long ARRAY_INT_INDEX_SCALE    = 4;
+  public static final long ARRAY_LONG_INDEX_SCALE   = 8;
+  public static final long ARRAY_FLOAT_INDEX_SCALE  = 4;
+  public static final long ARRAY_DOUBLE_INDEX_SCALE = 8;
+  public static final long ARRAY_OBJECT_INDEX_SCALE;  // varies, 4 or 8 depending on coop
+
+  //Used to convert "type" to bytes:  bytes = longs << LONG_SHIFT
+  public static final int BOOLEAN_SHIFT    = 0;
+  public static final int BYTE_SHIFT       = 0;
+  public static final long SHORT_SHIFT     = 1;
+  public static final long CHAR_SHIFT      = 1;
+  public static final long INT_SHIFT       = 2;
+  public static final long LONG_SHIFT      = 3;
+  public static final long FLOAT_SHIFT     = 2;
+  public static final long DOUBLE_SHIFT    = 3;
+  public static final long OBJECT_SHIFT;     // varies, 2 or 3 depending on coop
+
+  public static final String LS = System.getProperty("line.separator");
+
+  //@formatter:on
+
+  static {
+    try {
+      final Constructor<Unsafe> unsafeConstructor = Unsafe.class.getDeclaredConstructor();
+      unsafeConstructor.setAccessible(true);
+      unsafe = unsafeConstructor.newInstance();
+
+      // Alternative, but may not work across different JVMs.
+      //      Field field = Unsafe.class.getDeclaredField("theUnsafe");
+      //      field.setAccessible(true);
+      //      unsafe = (Unsafe) field.get(null);
+
+    } catch (final InstantiationException | IllegalAccessException | IllegalArgumentException
+        | InvocationTargetException | NoSuchMethodException e) {
+      e.printStackTrace();
+      throw new RuntimeException("Unable to acquire Unsafe. " + e);
+    }
+
+    //4 on 32-bit systems. 4 on 64-bit systems < 32GB, otherwise 8.
+    //This alone is not an indicator of compressed ref (coop)
+    ADDRESS_SIZE = unsafe.addressSize();
+    PAGE_SIZE = unsafe.pageSize();
+
+    ARRAY_BOOLEAN_BASE_OFFSET = unsafe.arrayBaseOffset(boolean[].class);
+    ARRAY_BYTE_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
+    ARRAY_SHORT_BASE_OFFSET = unsafe.arrayBaseOffset(short[].class);
+    ARRAY_CHAR_BASE_OFFSET = unsafe.arrayBaseOffset(char[].class);
+    ARRAY_INT_BASE_OFFSET = unsafe.arrayBaseOffset(int[].class);
+    ARRAY_LONG_BASE_OFFSET = unsafe.arrayBaseOffset(long[].class);
+    ARRAY_FLOAT_BASE_OFFSET = unsafe.arrayBaseOffset(float[].class);
+    ARRAY_DOUBLE_BASE_OFFSET = unsafe.arrayBaseOffset(double[].class);
+    ARRAY_OBJECT_BASE_OFFSET = unsafe.arrayBaseOffset(Object[].class);
+
+    ARRAY_OBJECT_INDEX_SCALE = unsafe.arrayIndexScale(Object[].class);
+    OBJECT_SHIFT = ARRAY_OBJECT_INDEX_SCALE == 4 ? 2 : 3;
+
+    final String jdkVer = System.getProperty("java.version");
+    final int[] p = parseJavaVersion(jdkVer);
+    JDK = p[0] + "." + p[1];
+    JDK_MAJOR = (p[0] == 1) ? p[1] : p[0];
+  }
+
+  private UnsafeUtil() {}
+
+  /**
+   * Returns first two number groups of the java version string.
+   * @param jdkVer the java version string from System.getProperty("java.version").
+   * @return first two number groups of the java version string.
+   */
+  public static int[] parseJavaVersion(final String jdkVer) {
+    final int p0, p1;
+    try {
+      String[] parts = jdkVer.trim().split("[^0-9\\.]");//grab only number groups and "."
+      parts = parts[0].split("\\."); //split out the number groups
+      p0 = Integer.parseInt(parts[0]); //the first number group
+      p1 = (parts.length > 1) ? Integer.parseInt(parts[1]) : 0; //2nd number group, or 0
+    } catch (final NumberFormatException | ArrayIndexOutOfBoundsException  e) {
+      throw new IllegalArgumentException("Improper Java -version string: " + jdkVer + "\n" + e);
+    }
+    //checkJavaVersion(jdkVer, p0, p1); //TODO Optional to omit this.
+    return new int[] {p0, p1};
+  }
+
+  public static void checkJavaVersion(final String jdkVer, final int p0, final int p1) {
+    if ( (p0 < 1) || ((p0 == 1) && (p1 < 8)) || (p0 > 13)  ) {
+      throw new IllegalArgumentException(
+          "Unsupported JDK Major Version, must be one of 1.8, 8, 9, 10, 11, 12, 13: " + jdkVer);
+    }
+  }
+
+  public static long getFieldOffset(final Class<?> c, final String fieldName) {
+    try {
+      return unsafe.objectFieldOffset(c.getDeclaredField(fieldName));
+    } catch (final NoSuchFieldException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  /**
+   * Like {@link Unsafe#arrayBaseOffset(Class)}, but caches return values for common array types.
+   * Useful because calling {@link Unsafe#arrayBaseOffset(Class)} directly incurs more overhead.
+   * @param c The given Class&lt;?&gt;.
+   * @return the base-offset
+   */
+  public static long getArrayBaseOffset(final Class<?> c) {
+    // Ordering here is roughly in order of what we expect to be most popular.
+    if (c == byte[].class) {
+      return ARRAY_BYTE_BASE_OFFSET;
+    } else if (c == int[].class) {
+      return ARRAY_INT_BASE_OFFSET;
+    } else if (c == long[].class) {
+      return ARRAY_LONG_BASE_OFFSET;
+    } else if (c == float[].class) {
+      return ARRAY_FLOAT_BASE_OFFSET;
+    } else if (c == double[].class) {
+      return ARRAY_DOUBLE_BASE_OFFSET;
+    } else if (c == boolean[].class) {
+      return ARRAY_BOOLEAN_BASE_OFFSET;
+    } else if (c == short[].class) {
+      return ARRAY_SHORT_BASE_OFFSET;
+    } else if (c == char[].class) {
+      return ARRAY_CHAR_BASE_OFFSET;
+    } else if (c == Object[].class) {
+      return ARRAY_OBJECT_BASE_OFFSET;
+    } else {
+      return unsafe.arrayBaseOffset(c);
+    }
+  }
+
+  public static long pageCount(final long bytes) {
+    return (int)((bytes + PAGE_SIZE) - 1L) / PAGE_SIZE;
+  }
+
+  /**
+   * Assert the requested offset and length against the allocated size.
+   * The invariants equation is: {@code 0 <= reqOff <= reqLen <= reqOff + reqLen <= allocSize}.
+   * If this equation is violated and assertions are enabled, an {@link AssertionError} will
+   * be thrown.
+   * @param reqOff the requested offset
+   * @param reqLen the requested length
+   * @param allocSize the allocated size.
+   */
+  public static void assertBounds(final long reqOff, final long reqLen, final long allocSize) {
+    assert ((reqOff | reqLen | (reqOff + reqLen) | (allocSize - (reqOff + reqLen))) >= 0) :
+      "reqOffset: " + reqOff + ", reqLength: " + reqLen
+      + ", (reqOff + reqLen): " + (reqOff + reqLen) + ", allocSize: " + allocSize;
+  }
+
+  /**
+   * Check the requested offset and length against the allocated size.
+   * The invariants equation is: {@code 0 <= reqOff <= reqLen <= reqOff + reqLen <= allocSize}.
+   * If this equation is violated an {@link IllegalArgumentException} will be thrown.
+   * @param reqOff the requested offset
+   * @param reqLen the requested length
+   * @param allocSize the allocated size.
+   */
+  public static void checkBounds(final long reqOff, final long reqLen, final long allocSize) {
+    if ((reqOff | reqLen | (reqOff + reqLen) | (allocSize - (reqOff + reqLen))) < 0) {
+      throw new IllegalArgumentException(
+          "reqOffset: " + reqOff + ", reqLength: " + reqLen
+              + ", (reqOff + reqLen): " + (reqOff + reqLen) + ", allocSize: " + allocSize);
+    }
+  }
+}
diff --git a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Util.java
similarity index 94%
copy from datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java
copy to datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Util.java
index d8768a4..0c02198 100644
--- a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Util.java
@@ -19,6 +19,8 @@
 
 package org.apache.datasketches.memory.internal;
 
+import static jdk.incubator.foreign.MemoryAccess.getLongAtIndex;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
@@ -29,7 +31,7 @@ import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.Random;
 
-import org.apache.datasketches.memory.Memory;
+import jdk.incubator.foreign.MemorySegment;
 
 /**
  * @author Lee Rhodes
@@ -81,9 +83,9 @@ public final class Util {
    * search algorithm. The range must be sorted method) prior to making this call.
    * If it is not sorted, the results are undefined. If the range contains
    * multiple elements with the specified value, there is no guarantee which one will be found.
-   * @param mem the Memory to be searched
-   * @param fromLongIndex the index of the first element (inclusive) to be searched
-   * @param toLongIndex the index of the last element (exclusive) to be searched
+   * @param seg the MemorySegment to be searched
+   * @param fromLongIndex the index of the first long element (inclusive) to be searched
+   * @param toLongIndex the index of the last long element (exclusive) to be searched
    * @param key the value to be searched for
    * @return index of the search key, if it is contained in the array within the specified range;
    * otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which
@@ -91,15 +93,15 @@ public final class Util {
    * than the key, or toIndex if all elements in the range are less than the specified key.
    * Note that this guarantees that the return value will be &ge; 0 if and only if the key is found.
    */
-  public static long binarySearchLongs(final Memory mem, final long fromLongIndex,
+  public static long binarySearchLongs(final MemorySegment seg, final long fromLongIndex,
       final long toLongIndex, final long key) {
-    UnsafeUtil.checkBounds(fromLongIndex << 3, (toLongIndex - fromLongIndex) << 3, mem.getCapacity());
+    UnsafeUtil.checkBounds(fromLongIndex << 3, (toLongIndex - fromLongIndex) << 3, seg.byteSize());
     long low = fromLongIndex;
     long high = toLongIndex - 1L;
 
     while (low <= high) {
       final long mid = (low + high) >>> 1;
-      final long midVal = mem.getLong(mid << 3);
+      final long midVal = getLongAtIndex(seg, mid);
 
       if (midVal < key)      { low = mid + 1;  }
       else if (midVal > key) { high = mid - 1; }
@@ -224,7 +226,7 @@ public final class Util {
      * Fills the given array with random valid Code Points from 0, inclusive, to
      * <i>Character.MAX_CODE_POINT</i>, inclusive.
      * The surrogate range, which is from <i>Character.MIN_SURROGATE</i>, inclusive, to
-     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <u>excluded</u>.
+     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <i>excluded</i>.
      * @param cpArr the array to fill
      */
     public final void fillCodePointArray(final int[] cpArr) {
@@ -235,7 +237,7 @@ public final class Util {
      * Fills the given array with random valid Code Points from <i>startCP</i>, inclusive, to
      * <i>endCP</i>, exclusive.
      * The surrogate range, which is from <i>Character.MIN_SURROGATE</i>, inclusive, to
-     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <u>excluded</u>.
+     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <i>excluded</i>.
      * @param cpArr the array to fill
      * @param startCP the starting Code Point, included.
      * @param endCP the ending Code Point, excluded. This value cannot exceed 0x110000.
@@ -257,7 +259,7 @@ public final class Util {
      * Return a single valid random Code Point from 0, inclusive, to
      * <i>Character.MAX_CODE_POINT</i>, inclusive.
      * The surrogate range, which is from <i>Character.MIN_SURROGATE</i>, inclusive, to
-     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <u>excluded</u>.
+     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <i>excluded</i>.
      * @return a single valid random CodePoint.
      */
     public final int getCodePoint() {
@@ -268,7 +270,7 @@ public final class Util {
      * Return a single valid random Code Point from <i>startCP</i>, inclusive, to
      * <i>endCP</i>, exclusive.
      * The surrogate range, which is from <i>Character.MIN_SURROGATE</i>, inclusive, to
-     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <u>excluded</u>.
+     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <i>excluded</i>.
      * @param startCP the starting Code Point, included.
      * @param endCP the ending Code Point, excluded. This value cannot exceed 0x110000.
      * @return a single valid random CodePoint.
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/package-info.java
similarity index 85%
copy from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
copy to datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/package-info.java
index aefdcba..769cee0 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/package-info.java
@@ -17,11 +17,4 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
-
-/**
- * Temporary class & placeholder. It may not be needed.
- */
-public class Dummy {
-
-}
+package org.apache.datasketches.memory;
diff --git a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/AllocateDirectMap.java b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/AllocateDirectMap.java
index 997e7e0..744b3af 100644
--- a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/AllocateDirectMap.java
+++ b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/AllocateDirectMap.java
@@ -67,13 +67,13 @@ class AllocateDirectMap implements Map {
   static final Method MAPPED_BYTE_BUFFER_FORCE0_METHOD;
 
   static {
-    try { //The FileChannelImpl methods map0 and unmap0 still exist in 16
+    try { //The FileChannelImpl methods map0 and unmap0 still exist in 17, however...
       FILE_CHANNEL_IMPL_MAP0_METHOD = FileChannelImpl.class
-          .getDeclaredMethod("map0", int.class, long.class, long.class); //JDK14 add boolean.class
+          .getDeclaredMethod("map0", int.class, long.class, long.class); //JDK14-17 adds boolean.class
       FILE_CHANNEL_IMPL_MAP0_METHOD.setAccessible(true);
 
       FILE_CHANNEL_IMPL_UNMAP0_METHOD = FileChannelImpl.class
-          .getDeclaredMethod("unmap0", long.class, long.class); //OK through jDK16
+          .getDeclaredMethod("unmap0", long.class, long.class); //OK through jDK17
       FILE_CHANNEL_IMPL_UNMAP0_METHOD.setAccessible(true);
 
 
diff --git a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
index 33053eb..88ffc01 100644
--- a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
+++ b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
@@ -22,11 +22,8 @@ package org.apache.datasketches.memory.internal;
 import sun.misc.Cleaner;
 
 /**
- * Extracts a version-dependent reference to the `sun.misc.Cleaner` into
- * a standalone class. The package name for Cleaner has changed in
- * later versions. The appropriate class will be loaded by the class loader
- * depending on the Java version that is used.
- * For more information, see: https://openjdk.java.net/jeps/238
+ * The package name and API for Cleaner changed from Java 8 to Java 9+.
+ * This extracts the java 8 `sun.misc.Cleaner` into a standalone class.
  */
 @SuppressWarnings("restriction")
 public class MemoryCleaner {
diff --git a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java
index d8768a4..2ec1d72 100644
--- a/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java
+++ b/datasketches-memory-java8/src/main/java/org/apache/datasketches/memory/internal/Util.java
@@ -224,7 +224,7 @@ public final class Util {
      * Fills the given array with random valid Code Points from 0, inclusive, to
      * <i>Character.MAX_CODE_POINT</i>, inclusive.
      * The surrogate range, which is from <i>Character.MIN_SURROGATE</i>, inclusive, to
-     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <u>excluded</u>.
+     * <i>Character.MAX_SURROGATE</i>, inclusive, is always <i>excluded</i>.
      * @param cpArr the array to fill
      */
     public final void fillCodePointArray(final int[] cpArr) {
diff --git a/datasketches-memory-java9/src/main/java/module-info.java b/datasketches-memory-java9/src/main/java/module-info.java
index 990b45d..78c9ce2 100644
--- a/datasketches-memory-java9/src/main/java/module-info.java
+++ b/datasketches-memory-java9/src/main/java/module-info.java
@@ -22,5 +22,5 @@ module org.apache.datasketches.memory {
     requires java.logging;
     requires jdk.unsupported;
 
-    exports org.apache.datasketches.memory.internal;
+    exports org.apache.datasketches.memory;
 }
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/Map.java
similarity index 57%
copy from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
copy to datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/Map.java
index aefdcba..6b99059 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/Map.java
@@ -17,11 +17,30 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
+package org.apache.datasketches.memory;
 
 /**
- * Temporary class & placeholder. It may not be needed.
+ * Read only interface for a memory mapped file
+ *
+ * @author Roman Leventov
+ * @author Lee Rhodes
+ * @author Praveenkumar Venkatesan
  */
-public class Dummy {
+public interface Map extends AutoCloseable {
+
+  /**
+   * @see <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/MappedByteBuffer.html#load--">
+   * java/nio/MappedByteBuffer.load</a>
+   */
+  void load();
+
+  /**
+   * @return true if loaded
+   *
+   * @see <a href=
+   * "https://docs.oracle.com/javase/8/docs/api/java/nio/MappedByteBuffer.html#isLoaded--"> java
+   * /nio/MappedByteBuffer.isLoaded</a>
+   */
+  boolean isLoaded();
 
 }
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/MemoryException.java
similarity index 55%
copy from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
copy to datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/MemoryException.java
index aefdcba..6b3663c 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/MemoryException.java
@@ -17,11 +17,26 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
+package org.apache.datasketches.memory;
 
 /**
- * Temporary class & placeholder. It may not be needed.
+ * Specific RuntimeExceptions for the datasketches-memory component.
+ *
+ * @author Lee Rhodes
+ *
  */
-public class Dummy {
+public class MemoryException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
 
+  /**
+   * Constructs a new runtime exception with the specified detail message. The cause is not
+   * initialized, and may subsequently be initialized by a call to
+   * Throwable.initCause(java.lang.Throwable).
+   *
+   * @param message the detail message. The detail message is saved for later retrieval by the
+   * Throwable.getMessage() method.
+   */
+  public MemoryException(final String message) {
+    super(message);
+  }
 }
diff --git a/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
index 448f365..ebadf9f 100644
--- a/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
+++ b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/internal/MemoryCleaner.java
@@ -19,32 +19,30 @@
 
 package org.apache.datasketches.memory.internal;
 
-import jdk.internal.ref.Cleaner;
+import java.lang.ref.Cleaner;
 
 /**
- * Extracts a version-dependent reference to the `jdk.internal.ref.Cleaner` into
- * a standalone class. The package name for Cleaner has changed in
- * later versions. The appropriate class will be loaded by the class loader
- * depending on the Java version that is used.
- * For more information, see: https://openjdk.java.net/jeps/238
+ * The package name and API for Cleaner changed from Java 8 to Java 9+.
+ * This extracts the java 9+ `java.lang.ref.Cleaner` into a standalone class.
  */
 public class MemoryCleaner {
-    private final Cleaner cleaner;
+  private final Cleaner cleaner = Cleaner.create();
+  private final Cleaner.Cleanable cleanable;
 
-    /**
-     * Creates a new `jdk.internal.ref.Cleaner`.
-     * @param referent the object to be cleaned
-     * @param deallocator - the cleanup code to be run when the cleaner is invoked.
-     * return MemoryCleaner
-     */
-    public MemoryCleaner(final Object referent, final Runnable deallocator) {
-        cleaner = Cleaner.create(referent, deallocator);
-    }
+  /**
+   * Creates a new `java.lang.ref.Cleaner`.
+   * @param referent the object to be cleaned
+   * @param deallocator - the cleanup code to be run when the cleaner is invoked.
+   * return MemoryCleaner
+   */
+  public MemoryCleaner(final Object referent, final Runnable deallocator) {
+      this.cleanable = cleaner.register(referent, deallocator);
+  }
 
-    /**
-     * Runs this cleaner, if it has not been run before.
-     */
+  /**
+   * Runs this cleaner, if it has not been run before.
+   */
     public void clean() {
-        cleaner.clean();
+        cleanable.clean();
     }
 }
diff --git a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/package-info.java
similarity index 85%
rename from datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
rename to datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/package-info.java
index aefdcba..769cee0 100644
--- a/datasketches-memory-java17/src/main/java/org/apache/datasketches/memory/internal/Dummy.java
+++ b/datasketches-memory-java9/src/main/java/org/apache/datasketches/memory/package-info.java
@@ -17,11 +17,4 @@
  * under the License.
  */
 
-package org.apache.datasketches.memory.internal;
-
-/**
- * Temporary class & placeholder. It may not be needed.
- */
-public class Dummy {
-
-}
+package org.apache.datasketches.memory;

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org