8224974: Implement JEP 352

Non-Volatile Mapped Byte Buffers

Reviewed-by: alanb, kvn, bpb, gromero, darcy, shade, bulasevich, dchuyko
This commit is contained in:
Andrew Dinn 2019-08-20 10:11:53 +01:00
parent db359f11b5
commit 047b8bfeb7
53 changed files with 1400 additions and 69 deletions

View file

@ -168,15 +168,16 @@ class Direct$Type$Buffer$RW$$BO$
//
protected Direct$Type$Buffer$RW$(int cap, long addr,
FileDescriptor fd,
Runnable unmapper)
Runnable unmapper,
boolean isSync)
{
#if[rw]
super(-1, 0, cap, cap, fd);
super(-1, 0, cap, cap, fd, isSync);
address = addr;
cleaner = Cleaner.create(this, unmapper);
att = null;
#else[rw]
super(cap, addr, fd, unmapper);
super(cap, addr, fd, unmapper, isSync);
this.isReadOnly = true;
#end[rw]
}

View file

@ -78,18 +78,33 @@ public abstract class MappedByteBuffer
// operations if valid; null if the buffer is not mapped.
private final FileDescriptor fd;
// A flag true if this buffer is mapped against non-volatile
// memory using one of the extended FileChannel.MapMode modes,
// MapMode.READ_ONLY_SYNC or MapMode.READ_WRITE_SYNC and false if
// it is mapped using any of the other modes. This flag only
// determines the behavior of force operations.
private final boolean isSync;
// This should only be invoked by the DirectByteBuffer constructors
//
MappedByteBuffer(int mark, int pos, int lim, int cap, // package-private
FileDescriptor fd)
{
FileDescriptor fd, boolean isSync) {
super(mark, pos, lim, cap);
this.fd = fd;
this.isSync = isSync;
}
MappedByteBuffer(int mark, int pos, int lim, int cap, // package-private
boolean isSync) {
super(mark, pos, lim, cap);
this.fd = null;
this.isSync = isSync;
}
MappedByteBuffer(int mark, int pos, int lim, int cap) { // package-private
super(mark, pos, lim, cap);
this.fd = null;
this.isSync = false;
}
// Returns the distance (in bytes) of the buffer start from the
@ -146,6 +161,23 @@ public abstract class MappedByteBuffer
return address & ~(pageSize - 1);
}
/**
* Tells whether this buffer was mapped against a non-volatile
* memory device by passing one of the sync map modes {@link
* jdk.nio.mapmode.ExtendedMapMode#READ_ONLY_SYNC
* ExtendedMapModeMapMode#READ_ONLY_SYNC} or {@link
* jdk.nio.mapmode.ExtendedMapMode#READ_ONLY_SYNC
* ExtendedMapMode#READ_WRITE_SYNC} in the call to {@link
* java.nio.channels.FileChannel#map FileChannel.map} or was
* mapped by passing one of the other map modes.
*
* @return true if the file was mapped using one of the sync map
* modes, otherwise false.
*/
private boolean isSync() {
return isSync;
}
/**
* Tells whether or not this buffer's content is resident in physical
* memory.
@ -168,6 +200,10 @@ public abstract class MappedByteBuffer
if (fd == null) {
return true;
}
// a sync mapped buffer is always loaded
if (isSync()) {
return true;
}
if ((address == 0) || (capacity() == 0))
return true;
long offset = mappingOffset();
@ -192,6 +228,10 @@ public abstract class MappedByteBuffer
if (fd == null) {
return this;
}
// no need to load a sync mapped buffer
if (isSync()) {
return this;
}
if ((address == 0) || (capacity() == 0))
return this;
long offset = mappingOffset();
@ -247,6 +287,9 @@ public abstract class MappedByteBuffer
if (fd == null) {
return this;
}
if (isSync) {
return force(0, limit());
}
if ((address != 0) && (capacity() != 0)) {
long offset = mappingOffset();
force0(fd, mappingAddress(offset), mappingLength(offset));
@ -303,8 +346,14 @@ public abstract class MappedByteBuffer
if ((address != 0) && (limit() != 0)) {
// check inputs
Objects.checkFromIndexSize(index, length, limit());
long offset = mappingOffset(index);
force0(fd, mappingAddress(offset, index), mappingLength(offset, length));
if (isSync) {
// simply force writeback of associated cache lines
Unsafe.getUnsafe().writebackMemory(address + index, length);
} else {
// force writeback via file descriptor
long offset = mappingOffset(index);
force0(fd, mappingAddress(offset, index), mappingLength(offset, length));
}
}
return this;
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.misc;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.channels.FileChannel.MapMode;
/**
* JDK-specific map modes implemented in java.base.
*/
public class ExtendedMapMode {
static final MethodHandle MAP_MODE_CONSTRUCTOR;
static {
try {
var lookup = MethodHandles.privateLookupIn(MapMode.class, MethodHandles.lookup());
var methodType = MethodType.methodType(void.class, String.class);
MAP_MODE_CONSTRUCTOR = lookup.findConstructor(MapMode.class, methodType);
} catch (Exception e) {
throw new InternalError(e);
}
}
public static final MapMode READ_ONLY_SYNC = newMapMode("READ_ONLY_SYNC");
public static final MapMode READ_WRITE_SYNC = newMapMode("READ_WRITE_SYNC");
private static MapMode newMapMode(String name) {
try {
return (MapMode) MAP_MODE_CONSTRUCTOR.invoke(name);
} catch (Throwable e) {
throw new InternalError(e);
}
}
private ExtendedMapMode() { }
}

View file

@ -921,6 +921,101 @@ public final class Unsafe {
checkPointer(null, address);
}
/**
* Ensure writeback of a specified virtual memory address range
* from cache to physical memory. All bytes in the address range
* are guaranteed to have been written back to physical memory on
* return from this call i.e. subsequently executed store
* instructions are guaranteed not to be visible before the
* writeback is completed.
*
* @param address
* the lowest byte address that must be guaranteed written
* back to memory. bytes at lower addresses may also be
* written back.
*
* @param length
* the length in bytes of the region starting at address
* that must be guaranteed written back to memory.
*
* @throws RuntimeException if memory writeback is not supported
* on the current hardware of if the arguments are invalid.
* (<em>Note:</em> after optimization, invalid inputs may
* go undetected, which will lead to unpredictable
* behavior)
*
* @since 14
*/
public void writebackMemory(long address, long length) {
checkWritebackEnabled();
checkWritebackMemory(address, length);
// perform any required pre-writeback barrier
writebackPreSync0();
// write back one cache line at a time
long line = dataCacheLineAlignDown(address);
long end = address + length;
while (line < end) {
writeback0(line);
line += dataCacheLineFlushSize();
}
// perform any required post-writeback barrier
writebackPostSync0();
}
/**
* Validate the arguments to writebackMemory
*
* @throws RuntimeException if the arguments are invalid
* (<em>Note:</em> after optimization, invalid inputs may
* go undetected, which will lead to unpredictable
* behavior)
*/
private void checkWritebackMemory(long address, long length) {
checkNativeAddress(address);
checkSize(length);
}
/**
* Validate that the current hardware supports memory writeback.
* (<em>Note:</em> this is a belt and braces check. Clients are
* expected to test whether writeback is enabled by calling
* ({@link isWritebackEnabled #isWritebackEnabled} and avoid
* calling method {@link writeback #writeback} if it is disabled).
*
*
* @throws RuntimeException if memory writeback is not supported
*/
private void checkWritebackEnabled() {
if (!isWritebackEnabled()) {
throw new RuntimeException("writebackMemory not enabled!");
}
}
/**
* force writeback of an individual cache line.
*
* @param address
* the start address of the cache line to be written back
*/
@HotSpotIntrinsicCandidate
private native void writeback0(long address);
/**
* Serialize writeback operations relative to preceding memory writes.
*/
@HotSpotIntrinsicCandidate
private native void writebackPreSync0();
/**
* Serialize writeback operations relative to following memory writes.
*/
@HotSpotIntrinsicCandidate
private native void writebackPostSync0();
/// random queries
/**
@ -1175,6 +1270,27 @@ public final class Unsafe {
*/
public int pageSize() { return PAGE_SIZE; }
/**
* Reports the size in bytes of a data cache line written back by
* the hardware cache line flush operation available to the JVM or
* 0 if data cache line flushing is not enabled.
*/
public int dataCacheLineFlushSize() { return DATA_CACHE_LINE_FLUSH_SIZE; }
/**
* Rounds down address to a data cache line boundary as
* determined by {@link #dataCacheLineFlushSize}
* @return the rounded down address
*/
public long dataCacheLineAlignDown(long address) {
return (address & ~(DATA_CACHE_LINE_FLUSH_SIZE - 1));
}
/**
* Returns true if data cache line writeback
*/
public static boolean isWritebackEnabled() { return DATA_CACHE_LINE_FLUSH_SIZE != 0; }
/// random trusted operations from JNI:
/**

View file

@ -1,4 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Red Hat Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -94,10 +95,28 @@ final class UnsafeConstants {
static final boolean UNALIGNED_ACCESS;
/**
* The size of an L1 data cache line which will be either a power
* of two or zero.
*
* <p>A non-zero value indicates that writeback to memory is
* enabled for the current processor. The value defines the
* natural alignment and size of any data cache line committed to
* memory by a single writeback operation. If data cache line
* writeback is not enabled for the current hardware the field
* will have value 0.
*
* @implNote
* The actual value for this field is injected by the JVM.
*/
static final int DATA_CACHE_LINE_FLUSH_SIZE;
static {
ADDRESS_SIZE0 = 0;
PAGE_SIZE = 0;
BIG_ENDIAN = false;
UNALIGNED_ACCESS = false;
DATA_CACHE_LINE_FLUSH_SIZE = 0;
}
}

View file

@ -192,6 +192,7 @@ module java.base {
jdk.compiler,
jdk.jfr,
jdk.jshell,
jdk.nio.mapmode,
jdk.scripting.nashorn,
jdk.scripting.nashorn.shell,
jdk.unsupported,

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, 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
@ -46,6 +46,8 @@ import java.nio.channels.WritableByteChannel;
import jdk.internal.access.JavaIOFileDescriptorAccess;
import jdk.internal.access.JavaNioAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.ExtendedMapMode;
import jdk.internal.misc.Unsafe;
import jdk.internal.ref.Cleaner;
import jdk.internal.ref.CleanerFactory;
@ -860,20 +862,15 @@ public class FileChannelImpl
// -- Memory-mapped buffers --
private static class Unmapper
private static abstract 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;
protected final long size;
protected final int cap;
private final FileDescriptor fd;
private Unmapper(long address, long size, int cap,
@ -884,12 +881,6 @@ public class FileChannelImpl
this.size = size;
this.cap = cap;
this.fd = fd;
synchronized (Unmapper.class) {
count++;
totalSize += size;
totalCapacity += cap;
}
}
public void run() {
@ -907,7 +898,63 @@ public class FileChannelImpl
}
}
synchronized (Unmapper.class) {
decrementStats();
}
protected abstract void incrementStats();
protected abstract void decrementStats();
}
private static class DefaultUnmapper extends Unmapper {
// keep track of non-sync mapped buffer usage
static volatile int count;
static volatile long totalSize;
static volatile long totalCapacity;
public DefaultUnmapper(long address, long size, int cap,
FileDescriptor fd) {
super(address, size, cap, fd);
incrementStats();
}
protected void incrementStats() {
synchronized (DefaultUnmapper.class) {
count++;
totalSize += size;
totalCapacity += cap;
}
}
protected void decrementStats() {
synchronized (DefaultUnmapper.class) {
count--;
totalSize -= size;
totalCapacity -= cap;
}
}
}
private static class SyncUnmapper extends Unmapper {
// keep track of mapped buffer usage
static volatile int count;
static volatile long totalSize;
static volatile long totalCapacity;
public SyncUnmapper(long address, long size, int cap,
FileDescriptor fd) {
super(address, size, cap, fd);
incrementStats();
}
protected void incrementStats() {
synchronized (SyncUnmapper.class) {
count++;
totalSize += size;
totalCapacity += cap;
}
}
protected void decrementStats() {
synchronized (SyncUnmapper.class) {
count--;
totalSize -= size;
totalCapacity -= cap;
@ -941,18 +988,30 @@ public class FileChannelImpl
throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
int imode;
boolean isSync = false;
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;
else
else if (mode == ExtendedMapMode.READ_ONLY_SYNC) {
imode = MAP_RO;
isSync = true;
} else if (mode == ExtendedMapMode.READ_WRITE_SYNC) {
imode = MAP_RW;
isSync = true;
} else {
throw new UnsupportedOperationException();
if ((mode != MapMode.READ_ONLY) && !writable)
}
if ((mode != MapMode.READ_ONLY) && mode != ExtendedMapMode.READ_ONLY_SYNC && !writable)
throw new NonWritableChannelException();
if (!readable)
throw new NonReadableChannelException();
// reject SYNC request if writeback is not enabled for this platform
if (isSync && !Unsafe.isWritebackEnabled()) {
throw new UnsupportedOperationException();
}
long addr = -1;
int ti = -1;
@ -990,9 +1049,9 @@ public class FileChannelImpl
// a valid file descriptor is not required
FileDescriptor dummy = new FileDescriptor();
if ((!writable) || (imode == MAP_RO))
return Util.newMappedByteBufferR(0, 0, dummy, null);
return Util.newMappedByteBufferR(0, 0, dummy, null, isSync);
else
return Util.newMappedByteBuffer(0, 0, dummy, null);
return Util.newMappedByteBuffer(0, 0, dummy, null, isSync);
}
pagePosition = (int)(position % allocationGranularity);
@ -1000,7 +1059,7 @@ public class FileChannelImpl
mapSize = size + pagePosition;
try {
// If map0 did not throw an exception, the address is valid
addr = map0(imode, mapPosition, mapSize);
addr = map0(imode, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError x) {
// An OutOfMemoryError may indicate that we've exhausted
// memory so force gc and re-attempt map
@ -1011,7 +1070,7 @@ public class FileChannelImpl
Thread.currentThread().interrupt();
}
try {
addr = map0(imode, mapPosition, mapSize);
addr = map0(imode, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError y) {
// After a second OOME, fail
throw new IOException("Map failed", y);
@ -1032,17 +1091,21 @@ public class FileChannelImpl
assert (IOStatus.checkAll(addr));
assert (addr % allocationGranularity == 0);
int isize = (int)size;
Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
Unmapper um = (isSync
? new SyncUnmapper(addr, mapSize, isize, mfd)
: new DefaultUnmapper(addr, mapSize, isize, mfd));
if ((!writable) || (imode == MAP_RO)) {
return Util.newMappedByteBufferR(isize,
addr + pagePosition,
mfd,
um);
um,
isSync);
} else {
return Util.newMappedByteBuffer(isize,
addr + pagePosition,
mfd,
um);
um,
isSync);
}
} finally {
threads.remove(ti);
@ -1062,15 +1125,40 @@ public class FileChannelImpl
}
@Override
public long getCount() {
return Unmapper.count;
return DefaultUnmapper.count;
}
@Override
public long getTotalCapacity() {
return Unmapper.totalCapacity;
return DefaultUnmapper.totalCapacity;
}
@Override
public long getMemoryUsed() {
return Unmapper.totalSize;
return DefaultUnmapper.totalSize;
}
};
}
/**
* Invoked by sun.management.ManagementFactoryHelper to create the management
* interface for sync mapped buffers.
*/
public static JavaNioAccess.BufferPool getSyncMappedBufferPool() {
return new JavaNioAccess.BufferPool() {
@Override
public String getName() {
return "mapped - 'non-volatile memory'";
}
@Override
public long getCount() {
return SyncUnmapper.count;
}
@Override
public long getTotalCapacity() {
return SyncUnmapper.totalCapacity;
}
@Override
public long getMemoryUsed() {
return SyncUnmapper.totalSize;
}
};
}
@ -1196,7 +1284,7 @@ public class FileChannelImpl
// -- Native methods --
// Creates a new mapping
private native long map0(int prot, long position, long length)
private native long map0(int prot, long position, long length, boolean isSync)
throws IOException;
// Removes an existing mapping

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, 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
@ -415,7 +415,8 @@ public class Util {
new Class<?>[] { int.class,
long.class,
FileDescriptor.class,
Runnable.class });
Runnable.class,
boolean.class });
ctor.setAccessible(true);
directByteBufferConstructor = ctor;
} catch (ClassNotFoundException |
@ -430,7 +431,8 @@ public class Util {
static MappedByteBuffer newMappedByteBuffer(int size, long addr,
FileDescriptor fd,
Runnable unmapper)
Runnable unmapper,
boolean isSync)
{
MappedByteBuffer dbb;
if (directByteBufferConstructor == null)
@ -440,7 +442,8 @@ public class Util {
new Object[] { size,
addr,
fd,
unmapper });
unmapper,
isSync});
} catch (InstantiationException |
IllegalAccessException |
InvocationTargetException e) {
@ -460,7 +463,8 @@ public class Util {
new Class<?>[] { int.class,
long.class,
FileDescriptor.class,
Runnable.class });
Runnable.class,
boolean.class });
ctor.setAccessible(true);
directByteBufferRConstructor = ctor;
} catch (ClassNotFoundException |
@ -475,7 +479,8 @@ public class Util {
static MappedByteBuffer newMappedByteBufferR(int size, long addr,
FileDescriptor fd,
Runnable unmapper)
Runnable unmapper,
boolean isSync)
{
MappedByteBuffer dbb;
if (directByteBufferRConstructor == null)
@ -485,7 +490,8 @@ public class Util {
new Object[] { size,
addr,
fd,
unmapper });
unmapper,
isSync});
} catch (InstantiationException |
IllegalAccessException |
InvocationTargetException e) {