src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java
changeset 47216 71c04702a3d5
parent 42447 36db92901ae9
child 47235 9ef10c6e67b8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1241 @@
+/*
+ * Copyright (c) 2000, 2016, 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 sun.nio.ch;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.FileLockInterruptionException;
+import java.nio.channels.NonReadableChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.ArrayList;
+import java.util.List;
+
+import jdk.internal.misc.JavaIOFileDescriptorAccess;
+import jdk.internal.misc.JavaNioAccess;
+import jdk.internal.misc.SharedSecrets;
+import jdk.internal.ref.Cleaner;
+import sun.security.action.GetPropertyAction;
+
+public class FileChannelImpl
+    extends FileChannel
+{
+    // Memory allocation size for mapping buffers
+    private static final long allocationGranularity;
+
+    // Access to FileDispatcher internals
+    private static final JavaIOFileDescriptorAccess fdAccess =
+        SharedSecrets.getJavaIOFileDescriptorAccess();
+
+    // Used to make native read and write calls
+    private final FileDispatcher nd;
+
+    // File descriptor
+    private final FileDescriptor fd;
+
+    // File access mode (immutable)
+    private final boolean writable;
+    private final boolean readable;
+
+    // Required to prevent finalization of creating stream (immutable)
+    private final Object parent;
+
+    // The path of the referenced file
+    // (null if the parent stream is created with a file descriptor)
+    private final String path;
+
+    // Thread-safe set of IDs of native threads, for signalling
+    private final NativeThreadSet threads = new NativeThreadSet(2);
+
+    // Lock for operations involving position and size
+    private final Object positionLock = new Object();
+
+    // Positional-read is not interruptible
+    private volatile boolean uninterruptible;
+
+    private FileChannelImpl(FileDescriptor fd, String path, boolean readable,
+                            boolean writable, Object parent)
+    {
+        this.fd = fd;
+        this.readable = readable;
+        this.writable = writable;
+        this.parent = parent;
+        this.path = path;
+        this.nd = new FileDispatcherImpl();
+    }
+
+    // Used by FileInputStream.getChannel(), FileOutputStream.getChannel
+    // and RandomAccessFile.getChannel()
+    public static FileChannel open(FileDescriptor fd, String path,
+                                   boolean readable, boolean writable,
+                                   Object parent)
+    {
+        return new FileChannelImpl(fd, path, readable, writable, parent);
+    }
+
+    private void ensureOpen() throws IOException {
+        if (!isOpen())
+            throw new ClosedChannelException();
+    }
+
+    public void setUninterruptible() {
+        uninterruptible = true;
+    }
+
+    // -- Standard channel operations --
+
+    protected void implCloseChannel() throws IOException {
+        if (!fd.valid())
+            return; // nothing to do
+
+        // Release and invalidate any locks that we still hold
+        if (fileLockTable != null) {
+            for (FileLock fl: fileLockTable.removeAll()) {
+                synchronized (fl) {
+                    if (fl.isValid()) {
+                        nd.release(fd, fl.position(), fl.size());
+                        ((FileLockImpl)fl).invalidate();
+                    }
+                }
+            }
+        }
+
+        // signal any threads blocked on this channel
+        threads.signalAndWait();
+
+        if (parent != null) {
+
+            // Close the fd via the parent stream's close method.  The parent
+            // will reinvoke our close method, which is defined in the
+            // superclass AbstractInterruptibleChannel, but the isOpen logic in
+            // that method will prevent this method from being reinvoked.
+            //
+            ((java.io.Closeable)parent).close();
+        } else {
+            nd.close(fd);
+        }
+
+    }
+
+    public int read(ByteBuffer dst) throws IOException {
+        ensureOpen();
+        if (!readable)
+            throw new NonReadableChannelException();
+        synchronized (positionLock) {
+            int n = 0;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return 0;
+                do {
+                    n = IOUtil.read(fd, dst, -1, nd);
+                } while ((n == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(n);
+            } finally {
+                threads.remove(ti);
+                end(n > 0);
+                assert IOStatus.check(n);
+            }
+        }
+    }
+
+    public long read(ByteBuffer[] dsts, int offset, int length)
+        throws IOException
+    {
+        if ((offset < 0) || (length < 0) || (offset > dsts.length - length))
+            throw new IndexOutOfBoundsException();
+        ensureOpen();
+        if (!readable)
+            throw new NonReadableChannelException();
+        synchronized (positionLock) {
+            long n = 0;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return 0;
+                do {
+                    n = IOUtil.read(fd, dsts, offset, length, nd);
+                } while ((n == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(n);
+            } finally {
+                threads.remove(ti);
+                end(n > 0);
+                assert IOStatus.check(n);
+            }
+        }
+    }
+
+    public int write(ByteBuffer src) throws IOException {
+        ensureOpen();
+        if (!writable)
+            throw new NonWritableChannelException();
+        synchronized (positionLock) {
+            int n = 0;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return 0;
+                do {
+                    n = IOUtil.write(fd, src, -1, nd);
+                } while ((n == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(n);
+            } finally {
+                threads.remove(ti);
+                end(n > 0);
+                assert IOStatus.check(n);
+            }
+        }
+    }
+
+    public long write(ByteBuffer[] srcs, int offset, int length)
+        throws IOException
+    {
+        if ((offset < 0) || (length < 0) || (offset > srcs.length - length))
+            throw new IndexOutOfBoundsException();
+        ensureOpen();
+        if (!writable)
+            throw new NonWritableChannelException();
+        synchronized (positionLock) {
+            long n = 0;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return 0;
+                do {
+                    n = IOUtil.write(fd, srcs, offset, length, nd);
+                } while ((n == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(n);
+            } finally {
+                threads.remove(ti);
+                end(n > 0);
+                assert IOStatus.check(n);
+            }
+        }
+    }
+
+    // -- Other operations --
+
+    public long position() throws IOException {
+        ensureOpen();
+        synchronized (positionLock) {
+            long p = -1;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return 0;
+                boolean append = fdAccess.getAppend(fd);
+                do {
+                    // in append-mode then position is advanced to end before writing
+                    p = (append) ? nd.size(fd) : position0(fd, -1);
+                } while ((p == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(p);
+            } finally {
+                threads.remove(ti);
+                end(p > -1);
+                assert IOStatus.check(p);
+            }
+        }
+    }
+
+    public FileChannel position(long newPosition) throws IOException {
+        ensureOpen();
+        if (newPosition < 0)
+            throw new IllegalArgumentException();
+        synchronized (positionLock) {
+            long p = -1;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return null;
+                do {
+                    p = position0(fd, newPosition);
+                } while ((p == IOStatus.INTERRUPTED) && isOpen());
+                return this;
+            } finally {
+                threads.remove(ti);
+                end(p > -1);
+                assert IOStatus.check(p);
+            }
+        }
+    }
+
+    public long size() throws IOException {
+        ensureOpen();
+        synchronized (positionLock) {
+            long s = -1;
+            int ti = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return -1;
+                do {
+                    s = nd.size(fd);
+                } while ((s == IOStatus.INTERRUPTED) && isOpen());
+                return IOStatus.normalize(s);
+            } finally {
+                threads.remove(ti);
+                end(s > -1);
+                assert IOStatus.check(s);
+            }
+        }
+    }
+
+    public FileChannel truncate(long newSize) throws IOException {
+        ensureOpen();
+        if (newSize < 0)
+            throw new IllegalArgumentException("Negative size");
+        if (!writable)
+            throw new NonWritableChannelException();
+        synchronized (positionLock) {
+            int rv = -1;
+            long p = -1;
+            int ti = -1;
+            long rp = -1;
+            try {
+                begin();
+                ti = threads.add();
+                if (!isOpen())
+                    return null;
+
+                // get current size
+                long size;
+                do {
+                    size = nd.size(fd);
+                } while ((size == IOStatus.INTERRUPTED) && isOpen());
+                if (!isOpen())
+                    return null;
+
+                // get current position
+                do {
+                    p = position0(fd, -1);
+                } while ((p == IOStatus.INTERRUPTED) && isOpen());
+                if (!isOpen())
+                    return null;
+                assert p >= 0;
+
+                // truncate file if given size is less than the current size
+                if (newSize < size) {
+                    do {
+                        rv = nd.truncate(fd, newSize);
+                    } while ((rv == IOStatus.INTERRUPTED) && isOpen());
+                    if (!isOpen())
+                        return null;
+                }
+
+                // if position is beyond new size then adjust it
+                if (p > newSize)
+                    p = newSize;
+                do {
+                    rp = position0(fd, p);
+                } while ((rp == IOStatus.INTERRUPTED) && isOpen());
+                return this;
+            } finally {
+                threads.remove(ti);
+                end(rv > -1);
+                assert IOStatus.check(rv);
+            }
+        }
+    }
+
+    public void force(boolean metaData) throws IOException {
+        ensureOpen();
+        int rv = -1;
+        int ti = -1;
+        try {
+            begin();
+            ti = threads.add();
+            if (!isOpen())
+                return;
+            do {
+                rv = nd.force(fd, metaData);
+            } while ((rv == IOStatus.INTERRUPTED) && isOpen());
+        } finally {
+            threads.remove(ti);
+            end(rv > -1);
+            assert IOStatus.check(rv);
+        }
+    }
+
+    // Assume at first that the underlying kernel supports sendfile();
+    // set this to false if we find out later that it doesn't
+    //
+    private static volatile boolean transferSupported = true;
+
+    // Assume that the underlying kernel sendfile() will work if the target
+    // fd is a pipe; set this to false if we find out later that it doesn't
+    //
+    private static volatile boolean pipeSupported = true;
+
+    // Assume that the underlying kernel sendfile() will work if the target
+    // fd is a file; set this to false if we find out later that it doesn't
+    //
+    private static volatile boolean fileSupported = true;
+
+    private long transferToDirectlyInternal(long position, int icount,
+                                            WritableByteChannel target,
+                                            FileDescriptor targetFD)
+        throws IOException
+    {
+        assert !nd.transferToDirectlyNeedsPositionLock() ||
+               Thread.holdsLock(positionLock);
+
+        long n = -1;
+        int ti = -1;
+        try {
+            begin();
+            ti = threads.add();
+            if (!isOpen())
+                return -1;
+            do {
+                n = transferTo0(fd, position, icount, targetFD);
+            } while ((n == IOStatus.INTERRUPTED) && isOpen());
+            if (n == IOStatus.UNSUPPORTED_CASE) {
+                if (target instanceof SinkChannelImpl)
+                    pipeSupported = false;
+                if (target instanceof FileChannelImpl)
+                    fileSupported = false;
+                return IOStatus.UNSUPPORTED_CASE;
+            }
+            if (n == IOStatus.UNSUPPORTED) {
+                // Don't bother trying again
+                transferSupported = false;
+                return IOStatus.UNSUPPORTED;
+            }
+            return IOStatus.normalize(n);
+        } finally {
+            threads.remove(ti);
+            end (n > -1);
+        }
+    }
+
+    private long transferToDirectly(long position, int icount,
+                                    WritableByteChannel target)
+        throws IOException
+    {
+        if (!transferSupported)
+            return IOStatus.UNSUPPORTED;
+
+        FileDescriptor targetFD = null;
+        if (target instanceof FileChannelImpl) {
+            if (!fileSupported)
+                return IOStatus.UNSUPPORTED_CASE;
+            targetFD = ((FileChannelImpl)target).fd;
+        } else if (target instanceof SelChImpl) {
+            // Direct transfer to pipe causes EINVAL on some configurations
+            if ((target instanceof SinkChannelImpl) && !pipeSupported)
+                return IOStatus.UNSUPPORTED_CASE;
+
+            // Platform-specific restrictions. Now there is only one:
+            // Direct transfer to non-blocking channel could be forbidden
+            SelectableChannel sc = (SelectableChannel)target;
+            if (!nd.canTransferToDirectly(sc))
+                return IOStatus.UNSUPPORTED_CASE;
+
+            targetFD = ((SelChImpl)target).getFD();
+        }
+
+        if (targetFD == null)
+            return IOStatus.UNSUPPORTED;
+        int thisFDVal = IOUtil.fdVal(fd);
+        int targetFDVal = IOUtil.fdVal(targetFD);
+        if (thisFDVal == targetFDVal) // Not supported on some configurations
+            return IOStatus.UNSUPPORTED;
+
+        if (nd.transferToDirectlyNeedsPositionLock()) {
+            synchronized (positionLock) {
+                long pos = position();
+                try {
+                    return transferToDirectlyInternal(position, icount,
+                                                      target, targetFD);
+                } finally {
+                    position(pos);
+                }
+            }
+        } else {
+            return transferToDirectlyInternal(position, icount, target, targetFD);
+        }
+    }
+
+    // Maximum size to map when using a mapped buffer
+    private static final long MAPPED_TRANSFER_SIZE = 8L*1024L*1024L;
+
+    private long transferToTrustedChannel(long position, long count,
+                                          WritableByteChannel target)
+        throws IOException
+    {
+        boolean isSelChImpl = (target instanceof SelChImpl);
+        if (!((target instanceof FileChannelImpl) || isSelChImpl))
+            return IOStatus.UNSUPPORTED;
+
+        // Trusted target: Use a mapped buffer
+        long remaining = count;
+        while (remaining > 0L) {
+            long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
+            try {
+                MappedByteBuffer dbb = map(MapMode.READ_ONLY, position, size);
+                try {
+                    // ## Bug: Closing this channel will not terminate the write
+                    int n = target.write(dbb);
+                    assert n >= 0;
+                    remaining -= n;
+                    if (isSelChImpl) {
+                        // one attempt to write to selectable channel
+                        break;
+                    }
+                    assert n > 0;
+                    position += n;
+                } finally {
+                    unmap(dbb);
+                }
+            } catch (ClosedByInterruptException e) {
+                // target closed by interrupt as ClosedByInterruptException needs
+                // to be thrown after closing this channel.
+                assert !target.isOpen();
+                try {
+                    close();
+                } catch (Throwable suppressed) {
+                    e.addSuppressed(suppressed);
+                }
+                throw e;
+            } catch (IOException ioe) {
+                // Only throw exception if no bytes have been written
+                if (remaining == count)
+                    throw ioe;
+                break;
+            }
+        }
+        return count - remaining;
+    }
+
+    private long transferToArbitraryChannel(long position, int icount,
+                                            WritableByteChannel target)
+        throws IOException
+    {
+        // Untrusted target: Use a newly-erased buffer
+        int c = Math.min(icount, TRANSFER_SIZE);
+        ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
+        long tw = 0;                    // Total bytes written
+        long pos = position;
+        try {
+            Util.erase(bb);
+            while (tw < icount) {
+                bb.limit(Math.min((int)(icount - tw), TRANSFER_SIZE));
+                int nr = read(bb, pos);
+                if (nr <= 0)
+                    break;
+                bb.flip();
+                // ## Bug: Will block writing target if this channel
+                // ##      is asynchronously closed
+                int nw = target.write(bb);
+                tw += nw;
+                if (nw != nr)
+                    break;
+                pos += nw;
+                bb.clear();
+            }
+            return tw;
+        } catch (IOException x) {
+            if (tw > 0)
+                return tw;
+            throw x;
+        } finally {
+            Util.releaseTemporaryDirectBuffer(bb);
+        }
+    }
+
+    public long transferTo(long position, long count,
+                           WritableByteChannel target)
+        throws IOException
+    {
+        ensureOpen();
+        if (!target.isOpen())
+            throw new ClosedChannelException();
+        if (!readable)
+            throw new NonReadableChannelException();
+        if (target instanceof FileChannelImpl &&
+            !((FileChannelImpl)target).writable)
+            throw new NonWritableChannelException();
+        if ((position < 0) || (count < 0))
+            throw new IllegalArgumentException();
+        long sz = size();
+        if (position > sz)
+            return 0;
+        int icount = (int)Math.min(count, Integer.MAX_VALUE);
+        if ((sz - position) < icount)
+            icount = (int)(sz - position);
+
+        long n;
+
+        // Attempt a direct transfer, if the kernel supports it
+        if ((n = transferToDirectly(position, icount, target)) >= 0)
+            return n;
+
+        // Attempt a mapped transfer, but only to trusted channel types
+        if ((n = transferToTrustedChannel(position, icount, target)) >= 0)
+            return n;
+
+        // Slow path for untrusted targets
+        return transferToArbitraryChannel(position, icount, target);
+    }
+
+    private long transferFromFileChannel(FileChannelImpl src,
+                                         long position, long count)
+        throws IOException
+    {
+        if (!src.readable)
+            throw new NonReadableChannelException();
+        synchronized (src.positionLock) {
+            long pos = src.position();
+            long max = Math.min(count, src.size() - pos);
+
+            long remaining = max;
+            long p = pos;
+            while (remaining > 0L) {
+                long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
+                // ## Bug: Closing this channel will not terminate the write
+                MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, size);
+                try {
+                    long n = write(bb, position);
+                    assert n > 0;
+                    p += n;
+                    position += n;
+                    remaining -= n;
+                } catch (IOException ioe) {
+                    // Only throw exception if no bytes have been written
+                    if (remaining == max)
+                        throw ioe;
+                    break;
+                } finally {
+                    unmap(bb);
+                }
+            }
+            long nwritten = max - remaining;
+            src.position(pos + nwritten);
+            return nwritten;
+        }
+    }
+
+    private static final int TRANSFER_SIZE = 8192;
+
+    private long transferFromArbitraryChannel(ReadableByteChannel src,
+                                              long position, long count)
+        throws IOException
+    {
+        // Untrusted target: Use a newly-erased buffer
+        int c = (int)Math.min(count, TRANSFER_SIZE);
+        ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
+        long tw = 0;                    // Total bytes written
+        long pos = position;
+        try {
+            Util.erase(bb);
+            while (tw < count) {
+                bb.limit((int)Math.min((count - tw), (long)TRANSFER_SIZE));
+                // ## Bug: Will block reading src if this channel
+                // ##      is asynchronously closed
+                int nr = src.read(bb);
+                if (nr <= 0)
+                    break;
+                bb.flip();
+                int nw = write(bb, pos);
+                tw += nw;
+                if (nw != nr)
+                    break;
+                pos += nw;
+                bb.clear();
+            }
+            return tw;
+        } catch (IOException x) {
+            if (tw > 0)
+                return tw;
+            throw x;
+        } finally {
+            Util.releaseTemporaryDirectBuffer(bb);
+        }
+    }
+
+    public long transferFrom(ReadableByteChannel src,
+                             long position, long count)
+        throws IOException
+    {
+        ensureOpen();
+        if (!src.isOpen())
+            throw new ClosedChannelException();
+        if (!writable)
+            throw new NonWritableChannelException();
+        if ((position < 0) || (count < 0))
+            throw new IllegalArgumentException();
+        if (position > size())
+            return 0;
+        if (src instanceof FileChannelImpl)
+           return transferFromFileChannel((FileChannelImpl)src,
+                                          position, count);
+
+        return transferFromArbitraryChannel(src, position, count);
+    }
+
+    public int read(ByteBuffer dst, long position) throws IOException {
+        if (dst == null)
+            throw new NullPointerException();
+        if (position < 0)
+            throw new IllegalArgumentException("Negative position");
+        if (!readable)
+            throw new NonReadableChannelException();
+        ensureOpen();
+        if (nd.needsPositionLock()) {
+            synchronized (positionLock) {
+                return readInternal(dst, position);
+            }
+        } else {
+            return readInternal(dst, position);
+        }
+    }
+
+    private int readInternal(ByteBuffer dst, long position) throws IOException {
+        assert !nd.needsPositionLock() || Thread.holdsLock(positionLock);
+        int n = 0;
+        int ti = -1;
+
+        boolean interruptible = !uninterruptible;
+        try {
+            if (interruptible) begin();
+            ti = threads.add();
+            if (!isOpen())
+                return -1;
+            do {
+                n = IOUtil.read(fd, dst, position, nd);
+            } while ((n == IOStatus.INTERRUPTED) && isOpen());
+            return IOStatus.normalize(n);
+        } finally {
+            threads.remove(ti);
+            if (interruptible) end(n > 0);
+            assert IOStatus.check(n);
+        }
+    }
+
+    public int write(ByteBuffer src, long position) throws IOException {
+        if (src == null)
+            throw new NullPointerException();
+        if (position < 0)
+            throw new IllegalArgumentException("Negative position");
+        if (!writable)
+            throw new NonWritableChannelException();
+        ensureOpen();
+        if (nd.needsPositionLock()) {
+            synchronized (positionLock) {
+                return writeInternal(src, position);
+            }
+        } else {
+            return writeInternal(src, position);
+        }
+    }
+
+    private int writeInternal(ByteBuffer src, long position) throws IOException {
+        assert !nd.needsPositionLock() || Thread.holdsLock(positionLock);
+        int n = 0;
+        int ti = -1;
+        try {
+            begin();
+            ti = threads.add();
+            if (!isOpen())
+                return -1;
+            do {
+                n = IOUtil.write(fd, src, position, nd);
+            } while ((n == IOStatus.INTERRUPTED) && isOpen());
+            return IOStatus.normalize(n);
+        } finally {
+            threads.remove(ti);
+            end(n > 0);
+            assert IOStatus.check(n);
+        }
+    }
+
+
+    // -- Memory-mapped buffers --
+
+    private static class Unmapper
+        implements Runnable
+    {
+        // may be required to close file
+        private static final NativeDispatcher nd = new FileDispatcherImpl();
+
+        // keep track of mapped buffer usage
+        static volatile int count;
+        static volatile long totalSize;
+        static volatile long totalCapacity;
+
+        private volatile long address;
+        private final long size;
+        private final int cap;
+        private final FileDescriptor fd;
+
+        private Unmapper(long address, long size, int cap,
+                         FileDescriptor fd)
+        {
+            assert (address != 0);
+            this.address = address;
+            this.size = size;
+            this.cap = cap;
+            this.fd = fd;
+
+            synchronized (Unmapper.class) {
+                count++;
+                totalSize += size;
+                totalCapacity += cap;
+            }
+        }
+
+        public void run() {
+            if (address == 0)
+                return;
+            unmap0(address, size);
+            address = 0;
+
+            // if this mapping has a valid file descriptor then we close it
+            if (fd.valid()) {
+                try {
+                    nd.close(fd);
+                } catch (IOException ignore) {
+                    // nothing we can do
+                }
+            }
+
+            synchronized (Unmapper.class) {
+                count--;
+                totalSize -= size;
+                totalCapacity -= cap;
+            }
+        }
+    }
+
+    private static void unmap(MappedByteBuffer bb) {
+        Cleaner cl = ((DirectBuffer)bb).cleaner();
+        if (cl != null)
+            cl.clean();
+    }
+
+    private static final int MAP_RO = 0;
+    private static final int MAP_RW = 1;
+    private static final int MAP_PV = 2;
+
+    public MappedByteBuffer map(MapMode mode, long position, long size)
+        throws IOException
+    {
+        ensureOpen();
+        if (mode == null)
+            throw new NullPointerException("Mode is null");
+        if (position < 0L)
+            throw new IllegalArgumentException("Negative position");
+        if (size < 0L)
+            throw new IllegalArgumentException("Negative size");
+        if (position + size < 0)
+            throw new IllegalArgumentException("Position + size overflow");
+        if (size > Integer.MAX_VALUE)
+            throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
+
+        int imode = -1;
+        if (mode == MapMode.READ_ONLY)
+            imode = MAP_RO;
+        else if (mode == MapMode.READ_WRITE)
+            imode = MAP_RW;
+        else if (mode == MapMode.PRIVATE)
+            imode = MAP_PV;
+        assert (imode >= 0);
+        if ((mode != MapMode.READ_ONLY) && !writable)
+            throw new NonWritableChannelException();
+        if (!readable)
+            throw new NonReadableChannelException();
+
+        long addr = -1;
+        int ti = -1;
+        try {
+            begin();
+            ti = threads.add();
+            if (!isOpen())
+                return null;
+
+            long mapSize;
+            int pagePosition;
+            synchronized (positionLock) {
+                long filesize;
+                do {
+                    filesize = nd.size(fd);
+                } while ((filesize == IOStatus.INTERRUPTED) && isOpen());
+                if (!isOpen())
+                    return null;
+
+                if (filesize < position + size) { // Extend file size
+                    if (!writable) {
+                        throw new IOException("Channel not open for writing " +
+                            "- cannot extend file to required size");
+                    }
+                    int rv;
+                    do {
+                        rv = nd.allocate(fd, position + size);
+                    } while ((rv == IOStatus.INTERRUPTED) && isOpen());
+                    if (!isOpen())
+                        return null;
+                }
+
+                if (size == 0) {
+                    addr = 0;
+                    // a valid file descriptor is not required
+                    FileDescriptor dummy = new FileDescriptor();
+                    if ((!writable) || (imode == MAP_RO))
+                        return Util.newMappedByteBufferR(0, 0, dummy, null);
+                    else
+                        return Util.newMappedByteBuffer(0, 0, dummy, null);
+                }
+
+                pagePosition = (int)(position % allocationGranularity);
+                long mapPosition = position - pagePosition;
+                mapSize = size + pagePosition;
+                try {
+                    // If map0 did not throw an exception, the address is valid
+                    addr = map0(imode, mapPosition, mapSize);
+                } catch (OutOfMemoryError x) {
+                    // An OutOfMemoryError may indicate that we've exhausted
+                    // memory so force gc and re-attempt map
+                    System.gc();
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException y) {
+                        Thread.currentThread().interrupt();
+                    }
+                    try {
+                        addr = map0(imode, mapPosition, mapSize);
+                    } catch (OutOfMemoryError y) {
+                        // After a second OOME, fail
+                        throw new IOException("Map failed", y);
+                    }
+                }
+            } // synchronized
+
+            // On Windows, and potentially other platforms, we need an open
+            // file descriptor for some mapping operations.
+            FileDescriptor mfd;
+            try {
+                mfd = nd.duplicateForMapping(fd);
+            } catch (IOException ioe) {
+                unmap0(addr, mapSize);
+                throw ioe;
+            }
+
+            assert (IOStatus.checkAll(addr));
+            assert (addr % allocationGranularity == 0);
+            int isize = (int)size;
+            Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
+            if ((!writable) || (imode == MAP_RO)) {
+                return Util.newMappedByteBufferR(isize,
+                                                 addr + pagePosition,
+                                                 mfd,
+                                                 um);
+            } else {
+                return Util.newMappedByteBuffer(isize,
+                                                addr + pagePosition,
+                                                mfd,
+                                                um);
+            }
+        } finally {
+            threads.remove(ti);
+            end(IOStatus.checkAll(addr));
+        }
+    }
+
+    /**
+     * Invoked by sun.management.ManagementFactoryHelper to create the management
+     * interface for mapped buffers.
+     */
+    public static JavaNioAccess.BufferPool getMappedBufferPool() {
+        return new JavaNioAccess.BufferPool() {
+            @Override
+            public String getName() {
+                return "mapped";
+            }
+            @Override
+            public long getCount() {
+                return Unmapper.count;
+            }
+            @Override
+            public long getTotalCapacity() {
+                return Unmapper.totalCapacity;
+            }
+            @Override
+            public long getMemoryUsed() {
+                return Unmapper.totalSize;
+            }
+        };
+    }
+
+    // -- Locks --
+
+
+
+    // keeps track of locks on this file
+    private volatile FileLockTable fileLockTable;
+
+    // indicates if file locks are maintained system-wide (as per spec)
+    private static boolean isSharedFileLockTable;
+
+    // indicates if the disableSystemWideOverlappingFileLockCheck property
+    // has been checked
+    private static volatile boolean propertyChecked;
+
+    // The lock list in J2SE 1.4/5.0 was local to each FileChannel instance so
+    // the overlap check wasn't system wide when there were multiple channels to
+    // the same file. This property is used to get 1.4/5.0 behavior if desired.
+    private static boolean isSharedFileLockTable() {
+        if (!propertyChecked) {
+            synchronized (FileChannelImpl.class) {
+                if (!propertyChecked) {
+                    String value = GetPropertyAction.privilegedGetProperty(
+                            "sun.nio.ch.disableSystemWideOverlappingFileLockCheck");
+                    isSharedFileLockTable = ((value == null) || value.equals("false"));
+                    propertyChecked = true;
+                }
+            }
+        }
+        return isSharedFileLockTable;
+    }
+
+    private FileLockTable fileLockTable() throws IOException {
+        if (fileLockTable == null) {
+            synchronized (this) {
+                if (fileLockTable == null) {
+                    if (isSharedFileLockTable()) {
+                        int ti = threads.add();
+                        try {
+                            ensureOpen();
+                            fileLockTable = FileLockTable.newSharedFileLockTable(this, fd);
+                        } finally {
+                            threads.remove(ti);
+                        }
+                    } else {
+                        fileLockTable = new SimpleFileLockTable();
+                    }
+                }
+            }
+        }
+        return fileLockTable;
+    }
+
+    public FileLock lock(long position, long size, boolean shared)
+        throws IOException
+    {
+        ensureOpen();
+        if (shared && !readable)
+            throw new NonReadableChannelException();
+        if (!shared && !writable)
+            throw new NonWritableChannelException();
+        FileLockImpl fli = new FileLockImpl(this, position, size, shared);
+        FileLockTable flt = fileLockTable();
+        flt.add(fli);
+        boolean completed = false;
+        int ti = -1;
+        try {
+            begin();
+            ti = threads.add();
+            if (!isOpen())
+                return null;
+            int n;
+            do {
+                n = nd.lock(fd, true, position, size, shared);
+            } while ((n == FileDispatcher.INTERRUPTED) && isOpen());
+            if (isOpen()) {
+                if (n == FileDispatcher.RET_EX_LOCK) {
+                    assert shared;
+                    FileLockImpl fli2 = new FileLockImpl(this, position, size,
+                                                         false);
+                    flt.replace(fli, fli2);
+                    fli = fli2;
+                }
+                completed = true;
+            }
+        } finally {
+            if (!completed)
+                flt.remove(fli);
+            threads.remove(ti);
+            try {
+                end(completed);
+            } catch (ClosedByInterruptException e) {
+                throw new FileLockInterruptionException();
+            }
+        }
+        return fli;
+    }
+
+    public FileLock tryLock(long position, long size, boolean shared)
+        throws IOException
+    {
+        ensureOpen();
+        if (shared && !readable)
+            throw new NonReadableChannelException();
+        if (!shared && !writable)
+            throw new NonWritableChannelException();
+        FileLockImpl fli = new FileLockImpl(this, position, size, shared);
+        FileLockTable flt = fileLockTable();
+        flt.add(fli);
+        int result;
+
+        int ti = threads.add();
+        try {
+            try {
+                ensureOpen();
+                result = nd.lock(fd, false, position, size, shared);
+            } catch (IOException e) {
+                flt.remove(fli);
+                throw e;
+            }
+            if (result == FileDispatcher.NO_LOCK) {
+                flt.remove(fli);
+                return null;
+            }
+            if (result == FileDispatcher.RET_EX_LOCK) {
+                assert shared;
+                FileLockImpl fli2 = new FileLockImpl(this, position, size,
+                                                     false);
+                flt.replace(fli, fli2);
+                return fli2;
+            }
+            return fli;
+        } finally {
+            threads.remove(ti);
+        }
+    }
+
+    void release(FileLockImpl fli) throws IOException {
+        int ti = threads.add();
+        try {
+            ensureOpen();
+            nd.release(fd, fli.position(), fli.size());
+        } finally {
+            threads.remove(ti);
+        }
+        assert fileLockTable != null;
+        fileLockTable.remove(fli);
+    }
+
+    // -- File lock support --
+
+    /**
+     * A simple file lock table that maintains a list of FileLocks obtained by a
+     * FileChannel. Use to get 1.4/5.0 behaviour.
+     */
+    private static class SimpleFileLockTable extends FileLockTable {
+        // synchronize on list for access
+        private final List<FileLock> lockList = new ArrayList<FileLock>(2);
+
+        public SimpleFileLockTable() {
+        }
+
+        private void checkList(long position, long size)
+            throws OverlappingFileLockException
+        {
+            assert Thread.holdsLock(lockList);
+            for (FileLock fl: lockList) {
+                if (fl.overlaps(position, size)) {
+                    throw new OverlappingFileLockException();
+                }
+            }
+        }
+
+        public void add(FileLock fl) throws OverlappingFileLockException {
+            synchronized (lockList) {
+                checkList(fl.position(), fl.size());
+                lockList.add(fl);
+            }
+        }
+
+        public void remove(FileLock fl) {
+            synchronized (lockList) {
+                lockList.remove(fl);
+            }
+        }
+
+        public List<FileLock> removeAll() {
+            synchronized(lockList) {
+                List<FileLock> result = new ArrayList<FileLock>(lockList);
+                lockList.clear();
+                return result;
+            }
+        }
+
+        public void replace(FileLock fl1, FileLock fl2) {
+            synchronized (lockList) {
+                lockList.remove(fl1);
+                lockList.add(fl2);
+            }
+        }
+    }
+
+    // -- Native methods --
+
+    // Creates a new mapping
+    private native long map0(int prot, long position, long length)
+        throws IOException;
+
+    // Removes an existing mapping
+    private static native int unmap0(long address, long length);
+
+    // Transfers from src to dst, or returns -2 if kernel can't do that
+    private native long transferTo0(FileDescriptor src, long position,
+                                    long count, FileDescriptor dst);
+
+    // Sets or reports this file's position
+    // If offset is -1, the current position is returned
+    // otherwise the position is set to offset
+    private native long position0(FileDescriptor fd, long offset);
+
+    // Caches fieldIDs
+    private static native long initIDs();
+
+    static {
+        IOUtil.load();
+        allocationGranularity = initIDs();
+    }
+
+}