Skip to content

Instantly share code, notes, and snippets.

@forax
Created February 16, 2014 21:41
Show Gist options
  • Save forax/9041030 to your computer and use it in GitHub Desktop.
Save forax/9041030 to your computer and use it in GitHub Desktop.
package java.lang.invoke;
import java.io.IOException;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import sun.misc.Unsafe;
public final class MappedArray {
private final FileChannel channel;
private final @Stable long addr;
private volatile boolean unmapped;
static final Object LOCK = new Object();
static int UNMAPPED_COUNT;
private static final sun.misc.Unsafe UNSAFE;
static final MethodHandle GET_BYTE, PUT_BYTE;
private static final SafeAccessCallSite GET_CS, PUT_CS;
private static final Method MAP, UNMAP;
static {
sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
Lookup lookup = MethodHandles.publicLookup();
MethodHandle getByte, putByte, checkUnmapped, fallback;
Class<?> unsafeClass = sun.misc.Unsafe.class;
try {
getByte = lookup.findVirtual(unsafeClass, "getByte", MethodType.methodType(byte.class, long.class));
putByte = lookup.findVirtual(unsafeClass, "putByte", MethodType.methodType(void.class, long.class, byte.class));
checkUnmapped = lookup.findStatic(MappedArray.class, "checkUnmapped", MethodType.methodType(void.class, MappedArray.class));
fallback = lookup.findVirtual(SafeAccessCallSite.class, "fallback", MethodType.methodType(void.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
getByte = MethodHandles.dropArguments(getByte.bindTo(unsafe), 0, MappedArray.class);
putByte = MethodHandles.dropArguments(putByte.bindTo(unsafe), 0, MappedArray.class);
SafeAccessCallSite getCallSite = new SafeAccessCallSite(getByte, checkUnmapped, fallback);
SafeAccessCallSite putCallSite = new SafeAccessCallSite(putByte, checkUnmapped, fallback);
Method map;
Method unmap;
Class<?> fileChannelClass = sun.nio.ch.FileChannelImpl.class;
try {
map = fileChannelClass.getDeclaredMethod("map0", int.class, long.class, long.class);
unmap = fileChannelClass.getDeclaredMethod("unmap0", long.class, long.class);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
map.setAccessible(true);
unmap.setAccessible(true);
UNSAFE = unsafe;
GET_BYTE = getCallSite.dynamicInvoker();
PUT_BYTE = putCallSite.dynamicInvoker();
GET_CS = getCallSite;
PUT_CS = putCallSite;
UNMAP = unmap;
MAP = map;
}
public static class SafeAccessCallSite extends MutableCallSite {
private final MethodHandle directAccess;
private final MethodHandle checkedAccess;
private final MethodHandle fallback;
SwitchPoint switchPoint;
SafeAccessCallSite(MethodHandle directAccess, MethodHandle checkUnmapped, MethodHandle fallback) {
super(directAccess.type());
this.directAccess = directAccess;
this.checkedAccess = MethodHandles.foldArguments(directAccess, checkUnmapped);
this.fallback = MethodHandles.foldArguments(checkedAccess, fallback.bindTo(this));
fallback();
}
public void fallback() {
synchronized(LOCK) {
System.err.println("fallback !");
MethodHandle access = (UNMAPPED_COUNT == 0)? directAccess: checkedAccess;
SwitchPoint switchPoint = new SwitchPoint();
this.switchPoint = switchPoint;
setTarget(switchPoint.guardWithTest(access, fallback));
}
}
}
public static void checkUnmapped(MappedArray array) {
if (array.unmapped) {
throw new IllegalStateException("array is unmapped !");
}
}
private MappedArray(FileChannel channel, long addr) {
this.channel = channel;
this.addr = addr;
}
public byte get(int index) {
//return UNSAFE.getByte(addr + index);
try {
return (byte)GET_BYTE.invokeExact(this, addr + (index & 0x7FFFFFFF));
} catch (Throwable e) {
UNSAFE.throwException(e);
throw null;
}
}
public void put(int index, byte value) {
//UNSAFE.putByte(addr + index, value);
try {
PUT_BYTE.invokeExact(this, addr + (index & 0x7FFFFFFF), value);
} catch (Throwable e) {
UNSAFE.throwException(e);
}
}
@Override
protected void finalize() throws Throwable {
synchronized(LOCK) {
if (!unmapped) {
return;
}
System.err.println("finalize UNMAPPED_COUNT " + UNMAPPED_COUNT);
if (--UNMAPPED_COUNT == 0) {
SwitchPoint.invalidateAll(new SwitchPoint[]{GET_CS.switchPoint, PUT_CS.switchPoint});
System.err.println("finalize: invalidate all !");
}
}
}
public void unmap() throws IOException {
synchronized(LOCK) {
unmapped = true;
if (UNMAPPED_COUNT++ == 0) {
SwitchPoint.invalidateAll(new SwitchPoint[]{GET_CS.switchPoint, PUT_CS.switchPoint});
System.err.println("unmap: invalidate all !");
}
try {
UNMAP.invoke(channel, addr, Integer.MAX_VALUE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException(e);
}
}
}
//DEBUG
@Override
public String toString() {
return channel + " mapped at " + Long.toHexString(addr);
}
public static MappedArray map(FileChannel channel) throws IOException {
if (channel.size() < Integer.MAX_VALUE) {
throw new IllegalArgumentException("channel must be bigger than Integer.MAX_VALUE");
}
long addr;
try {
// If no exception was thrown from map0, the address is valid
addr =(Long) MAP.invoke(channel, 2 /*PRIVATE*/, 0, Integer.MAX_VALUE);
} catch (OutOfMemoryError | IllegalAccessException | InvocationTargetException e) {
throw new IOException(e);
}
try {
return new MappedArray(channel, addr);
} catch(Throwable t) {
try {
UNMAP.invoke(channel, addr, Integer.MAX_VALUE);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
t.addSuppressed(e);
}
UNSAFE.throwException(t);
throw null;
}
}
}
import java.io.IOException;
import java.lang.invoke.MappedArray;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class Test2 {
private static MappedArray createArray() throws IOException {
Path path = Files.createTempFile(null, null);
try(FileChannel channel =
FileChannel.open(path,
StandardOpenOption.CREATE,
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.SPARSE,
StandardOpenOption.DELETE_ON_CLOSE)) {
channel.write(ByteBuffer.wrap(new byte[1]), Integer.MAX_VALUE);
channel.force(true);
return MappedArray.map(channel);
}
}
private static void test(MappedArray array1) throws IOException {
for(int i=0; i<Integer.MAX_VALUE; i++) {
array1.put(i, (byte)i);
}
}
public static void main(String[] args) throws IOException {
MappedArray array1 = createArray();
MappedArray array2 = createArray();
for(int j=0; j<5; j++) {
test(array1);
if (j == 0) {
array2.unmap();
array2 = null;
}
if (j == 1) {
System.gc();
System.gc();
}
}
}
}
@nitsanw
Copy link

nitsanw commented Dec 6, 2016

"Fields which are declared {@code final} may also be annotated as stable.
Since final fields already behave as stable values, such an annotation
indicates no additional information, unless the type of the field is
an array type."
So the @stable annotation on https://gist.github.com/forax/9041030#file-mappedarray-java-L13 is redundant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment