diff a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
@@ -113,37 +113,10 @@
      * @return the hash code value for this address.
      */
     @Override
     int hashCode();
 
-    /**
-     * Perform bulk copy from source address to target address. More specifically, the bytes at addresses {@code src}
-     * through {@code src.addOffset(bytes - 1)} are copied into addresses {@code dst} through {@code dst.addOffset(bytes - 1)}.
-     * If the source and address ranges overlap, then the copying is performed as if the bytes at addresses {@code src}
-     * through {@code src.addOffset(bytes - 1)} were first copied into a temporary segment with size {@code bytes},
-     * and then the contents of the temporary segment were copied into the bytes at addresses {@code dst} through
-     * {@code dst.addOffset(bytes - 1)}.
-     * <p>
-     * The result of a bulk copy is unspecified if, in the uncommon case, the source and target address ranges do not
-     * overlap, but refer to overlapping regions of the same backing storage using different addresses.  For example,
-     * this may occur if the same file is {@link MemorySegment#mapFromPath mapped} to two segments.
-     *
-     * @param src the source address.
-     * @param dst the target address.
-     * @param bytes the number of bytes to be copied.
-     * @throws IndexOutOfBoundsException if {@code bytes < 0}, or if it is greater than the size of the segments
-     * associated with either {@code src} or {@code dst}.
-     * @throws IllegalStateException if either the source address or the target address belong to memory segments
-     * which have been already closed, or if access occurs from a thread other than the thread owning either segment.
-     * @throws UnsupportedOperationException if either {@code src} or {@code dst} do not feature required access modes;
-     * more specifically, {@code src} should be associated with a segment with {@link MemorySegment#READ} access mode,
-     * while {@code dst} should be associated with a segment with {@link MemorySegment#WRITE} access mode.
-     */
-    static void copy(MemoryAddress src, MemoryAddress dst, long bytes) {
-        MemoryAddressImpl.copy((MemoryAddressImpl)src, (MemoryAddressImpl)dst, bytes);
-    }
-
     /**
      * The <em>unchecked</em> memory address instance modelling the {@code NULL} address. This address is <em>not</em> backed by
      * a memory segment and hence it cannot be dereferenced.
      */
     MemoryAddress NULL = new MemoryAddressImpl( 0L);
diff a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
@@ -29,10 +29,11 @@
 import java.nio.ByteBuffer;
 
 import jdk.internal.foreign.AbstractMemorySegmentImpl;
 import jdk.internal.foreign.HeapMemorySegmentImpl;
 import jdk.internal.foreign.MappedMemorySegmentImpl;
+import jdk.internal.foreign.MemoryAddressImpl;
 import jdk.internal.foreign.NativeMemorySegmentImpl;
 import jdk.internal.foreign.Utils;
 
 import java.io.IOException;
 import java.nio.channels.FileChannel;
@@ -306,10 +307,33 @@
      * thread owning this segment
      * @throws UnsupportedOperationException if this segment does not support the {@link #WRITE} access mode
      */
     MemorySegment fill(byte value);
 
+    /**
+     * Perform bulk copy from given source segment to this segment. More specifically, the bytes at
+     * offset {@code 0} through {@code src.byteSize() - 1} in the source segment are copied into this segment
+     * at offset {@code 0} through {@code src.byteSize() - 1}.
+     * If the source segment overlaps with this segment, then the copying is performed as if the bytes at
+     * offset {@code 0} through {@code src.byteSize() - 1} in the source segment were first copied into a
+     * temporary segment with size {@code bytes}, and then the contents of the temporary segment were copied into
+     * this segment at offset {@code 0} through {@code src.byteSize() - 1}.
+     * <p>
+     * The result of a bulk copy is unspecified if, in the uncommon case, the source segment does not overlap with
+     * this segment, but it instead refers to an overlapping regions of the same backing storage using different addresses.
+     * For example, this may occur if the same file is {@link MemorySegment#mapFromPath mapped} to two segments.
+     *
+     * @param src the source segment.
+     * @throws IndexOutOfBoundsException if {src.byteSize() > this.byteSize()}.
+     * @throws IllegalStateException if either the source segment or this segment have been already closed,
+     * or if access occurs from a thread other than the thread owning either segment.
+     * @throws UnsupportedOperationException if either the source segment or this segment do not feature required access modes;
+     * more specifically, {@code src} should feature at least the {@link MemorySegment#READ} access mode,
+     * while this segment should feature at least the {@link MemorySegment#WRITE} access mode.
+     */
+    void copyFrom(MemorySegment src);
+
     /**
      * Wraps this segment in a {@link ByteBuffer}. Some of the properties of the returned buffer are linked to
      * the properties of this segment. For instance, if this segment is <em>immutable</em>
      * (e.g. the segment has access mode {@link #READ} but not {@link #WRITE}), then the resulting buffer is <em>read-only</em>
      * (see {@link ByteBuffer#isReadOnly()}. Additionally, if this is a native memory segment, the resulting buffer is
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
@@ -121,10 +121,21 @@
         checkRange(0, length, true);
         UNSAFE.setMemory(base(), min(), length, value);
         return this;
     }
 
+    public void copyFrom(MemorySegment src) {
+        long size = src.byteSize();
+        ((AbstractMemorySegmentImpl)src).checkRange(0, size, true);
+        checkRange(0, size, false);
+        long offsetSrc = ((AbstractMemorySegmentImpl) src).min();
+        long offsetDst = min();
+        Object baseSrc = ((AbstractMemorySegmentImpl) src).base();
+        Object baseDst = base();
+        UNSAFE.copyMemory(baseSrc, offsetSrc, baseDst, offsetDst, size);
+    }
+
     @Override
     @ForceInline
     public final MemoryAddress baseAddress() {
         return new MemoryAddressImpl(this, 0);
     }
@@ -226,11 +237,11 @@
     @Override
     public final byte[] toByteArray() {
         checkIntSize("byte[]");
         byte[] arr = new byte[(int)length];
         MemorySegment arrSegment = MemorySegment.ofArray(arr);
-        MemoryAddress.copy(baseAddress(), arrSegment.baseAddress(), length);
+        arrSegment.copyFrom(this);
         return arr;
     }
 
     boolean isSmall() {
         return isSet(SMALL);
diff a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
@@ -52,21 +52,10 @@
     public MemoryAddressImpl(AbstractMemorySegmentImpl segment, long offset) {
         this.segment = Objects.requireNonNull(segment);
         this.offset = offset;
     }
 
-    public static void copy(MemoryAddressImpl src, MemoryAddressImpl dst, long size) {
-        src.checkAccess(0, size, true);
-        dst.checkAccess(0, size, false);
-        //check disjoint
-        long offsetSrc = src.unsafeGetOffset();
-        long offsetDst = dst.unsafeGetOffset();
-        Object baseSrc = src.unsafeGetBase();
-        Object baseDst = dst.unsafeGetBase();
-        UNSAFE.copyMemory(baseSrc, offsetSrc, baseDst, offsetDst, size);
-    }
-
     // MemoryAddress methods
 
     @Override
     public long segmentOffset() {
         if (segment() == null) {
diff a/test/jdk/java/foreign/TestByteBuffer.java b/test/jdk/java/foreign/TestByteBuffer.java
--- a/test/jdk/java/foreign/TestByteBuffer.java
+++ b/test/jdk/java/foreign/TestByteBuffer.java
@@ -427,11 +427,11 @@
         checkByteArrayAlignment(seq.elementLayout());
         int bytes = (int)seq.byteSize();
         try (MemorySegment nativeArray = MemorySegment.allocateNative(bytes);
              MemorySegment heapArray = MemorySegment.ofArray(new byte[bytes])) {
             initializer.accept(heapArray.baseAddress());
-            MemoryAddress.copy(heapArray.baseAddress(), nativeArray.baseAddress(), bytes);
+            nativeArray.copyFrom(heapArray);
             checker.accept(nativeArray.baseAddress());
         }
     }
 
     @Test(dataProvider="resizeOps")
@@ -439,11 +439,11 @@
         checkByteArrayAlignment(seq.elementLayout());
         int bytes = (int)seq.byteSize();
         try (MemorySegment nativeArray = MemorySegment.allocateNative(seq);
              MemorySegment heapArray = MemorySegment.ofArray(new byte[bytes])) {
             initializer.accept(nativeArray.baseAddress());
-            MemoryAddress.copy(nativeArray.baseAddress(), heapArray.baseAddress(), bytes);
+            heapArray.copyFrom(nativeArray);
             checker.accept(heapArray.baseAddress());
         }
     }
 
     @Test(dataProvider="bufferSources")
diff a/test/jdk/java/foreign/TestMemoryCopy.java b/test/jdk/java/foreign/TestMemoryCopy.java
--- a/test/jdk/java/foreign/TestMemoryCopy.java
+++ b/test/jdk/java/foreign/TestMemoryCopy.java
@@ -55,11 +55,11 @@
         }
         for (int i = 0 ; i < size ; i++) {
             BYTE_HANDLE.set(addr1.addOffset(i), (byte) i);
         }
         //perform copy
-        MemoryAddress.copy(addr1, addr2, size);
+        s2.segment.copyFrom(s1.segment.asSlice(0, size));
         //check that copy actually worked
         for (int i = 0 ; i < size ; i++) {
             assertEquals((byte)i, BYTE_HANDLE.get(addr2.addOffset(i)));
         }
     }
diff a/test/jdk/java/foreign/TestSegments.java b/test/jdk/java/foreign/TestSegments.java
--- a/test/jdk/java/foreign/TestSegments.java
+++ b/test/jdk/java/foreign/TestSegments.java
@@ -319,10 +319,11 @@
         final Object[] params;
 
         final static List<String> CONFINED_NAMES = List.of(
                 "close",
                 "fill",
+                "copyFrom",
                 "toByteArray",
                 "withOwnerThread"
         );
 
         public SegmentMember(Method method, Object[] params) {
diff a/test/micro/org/openjdk/bench/jdk/incubator/foreign/BulkOps.java b/test/micro/org/openjdk/bench/jdk/incubator/foreign/BulkOps.java
--- /dev/null
+++ b/test/micro/org/openjdk/bench/jdk/incubator/foreign/BulkOps.java
@@ -0,0 +1,94 @@
+/*
+ *  Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ *  This code is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 only, as
+ *  published by the Free Software Foundation.  Oracle designates this
+ *  particular file as subject to the "Classpath" exception as provided
+ *  by Oracle in the LICENSE file that accompanied this code.
+ *
+ *  This code is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ *  version 2 for more details (a copy is included in the LICENSE file that
+ *  accompanied this code).
+ *
+ *  You should have received a copy of the GNU General Public License version
+ *  2 along with this work; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ *  or visit www.oracle.com if you need additional information or have any
+ *  questions.
+ *
+ */
+
+package org.openjdk.bench.jdk.incubator.foreign;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import sun.misc.Unsafe;
+
+import jdk.incubator.foreign.MemorySegment;
+import java.util.concurrent.TimeUnit;
+
+import static jdk.incubator.foreign.MemoryLayouts.JAVA_INT;
+
+@BenchmarkMode(Mode.AverageTime)
+@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@State(org.openjdk.jmh.annotations.Scope.Thread)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@Fork(3)
+public class BulkOps {
+
+    static final Unsafe unsafe = Utils.unsafe;
+
+    static final int ELEM_SIZE = 1_000_000;
+    static final int CARRIER_SIZE = (int)JAVA_INT.byteSize();
+    static final int ALLOC_SIZE = ELEM_SIZE * CARRIER_SIZE;
+
+    static final long unsafe_addr = unsafe.allocateMemory(ALLOC_SIZE);
+    static final MemorySegment segment = MemorySegment.allocateNative(ALLOC_SIZE);
+
+    static final int[] bytes = new int[ELEM_SIZE];
+    static final MemorySegment bytesSegment = MemorySegment.ofArray(bytes);
+    static final int UNSAFE_INT_OFFSET = unsafe.arrayBaseOffset(int[].class);
+
+    static {
+        for (int i = 0 ; i < bytes.length ; i++) {
+            bytes[i] = i;
+        }
+    }
+
+    @Benchmark
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public void unsafe_fill() {
+        unsafe.setMemory(unsafe_addr, ALLOC_SIZE, (byte)0);
+    }
+
+    @Benchmark
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public void segment_fill() {
+        segment.fill((byte)0);
+    }
+
+    @Benchmark
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public void unsafe_copy() {
+        unsafe.copyMemory(bytes, UNSAFE_INT_OFFSET, null, unsafe_addr, ALLOC_SIZE);
+    }
+
+    @Benchmark
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public void segment_copy() {
+        segment.copyFrom(bytesSegment);
+    }
+}
