package com.ctriposs.bigmap.page; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MappedPageImpl implements IMappedPage, Closeable { private final static Logger logger = LoggerFactory.getLogger(MappedPageImpl.class); private ThreadLocalByteBuffer threadLocalBuffer; private volatile boolean dirty = false; private volatile boolean closed = false; private String pageFile; private long index; public MappedPageImpl(MappedByteBuffer mbb, String pageFile, long index) { this.threadLocalBuffer = new ThreadLocalByteBuffer(mbb); this.pageFile = pageFile; this.index = index; } public void close() throws IOException { synchronized(this) { if (closed) return; flush(); MappedByteBuffer srcBuf = (MappedByteBuffer)threadLocalBuffer.getSourceBuffer(); unmap(srcBuf); this.threadLocalBuffer = null; // hint GC closed = true; if (logger.isDebugEnabled()) { logger.debug("Mapped page for " + this.pageFile + " was just unmapped and closed."); } } } @Override public void setDirty(boolean dirty) { this.dirty = dirty; } @Override public void flush() { synchronized(this) { if (closed) return; if (dirty) { MappedByteBuffer srcBuf = (MappedByteBuffer)threadLocalBuffer.getSourceBuffer(); srcBuf.force(); // flush the changes dirty = false; if (logger.isDebugEnabled()) { logger.debug("Mapped page for " + this.pageFile + " was just flushed."); } } } } public byte[] getLocal(int position, int length) { ByteBuffer buf = this.getLocal(position); byte[] data = new byte[length]; buf.get(data); return data; } @Override public ByteBuffer getLocal(int position) { ByteBuffer buf = this.threadLocalBuffer.get(); buf.position(position); return buf; } @Override public ByteBuffer getLocal() { ByteBuffer buf = this.threadLocalBuffer.get(); return buf; } private static void unmap(MappedByteBuffer buffer) { Cleaner.clean(buffer); } /** * Helper class allowing to clean direct buffers. */ private static class Cleaner { public static final boolean CLEAN_SUPPORTED; private static final Method directBufferCleaner; private static final Method directBufferCleanerClean; static { Method directBufferCleanerX = null; Method directBufferCleanerCleanX = null; boolean v; try { directBufferCleanerX = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner"); directBufferCleanerX.setAccessible(true); directBufferCleanerCleanX = Class.forName("sun.misc.Cleaner").getMethod("clean"); directBufferCleanerCleanX.setAccessible(true); v = true; } catch (Exception e) { v = false; } CLEAN_SUPPORTED = v; directBufferCleaner = directBufferCleanerX; directBufferCleanerClean = directBufferCleanerCleanX; } public static void clean(ByteBuffer buffer) { if (buffer == null) return; if (CLEAN_SUPPORTED && buffer.isDirect()) { try { Object cleaner = directBufferCleaner.invoke(buffer); directBufferCleanerClean.invoke(cleaner); } catch (Exception e) { // silently ignore exception } } } } private static class ThreadLocalByteBuffer extends ThreadLocal<ByteBuffer> { private ByteBuffer _src; public ThreadLocalByteBuffer(ByteBuffer src) { _src = src; } public ByteBuffer getSourceBuffer() { return _src; } @Override protected synchronized ByteBuffer initialValue() { ByteBuffer dup = _src.duplicate(); return dup; } } @Override public boolean isClosed() { return closed; } public String toString() { return "Mapped page for " + this.pageFile + ", index = " + this.index + "."; } @Override public String getPageFile() { return this.pageFile; } @Override public long getPageIndex() { return this.index; } }