View Javadoc
1   package com.github.davidmoten.rx2.internal.flowable.buffertofile;
2   
3   import java.io.File;
4   import java.io.RandomAccessFile;
5   import java.lang.reflect.InvocationTargetException;
6   import java.lang.reflect.Method;
7   import java.nio.channels.FileChannel;
8   
9   import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
10  
11  import sun.misc.Unsafe;
12  import sun.nio.ch.FileChannelImpl;
13  
14  @SuppressWarnings("restriction")
15  public final class MemoryMappedFile {
16  
17      private static final Unsafe unsafe;
18      private static final Method mmap;
19      private static final Method unmmap;
20      private static final int BYTE_ARRAY_OFFSET;
21  
22      private final File file;
23      private final long size;
24      private long addr;
25  
26      static {
27          unsafe = UnsafeAccess.unsafe();
28          mmap = getMethod(FileChannelImpl.class, "map0", int.class, long.class, long.class);
29          unmmap = getMethod(FileChannelImpl.class, "unmap0", long.class, long.class);
30          BYTE_ARRAY_OFFSET = unsafe.arrayBaseOffset(byte[].class);
31      }
32  
33      public MemoryMappedFile(File file, long len) {
34          this.file = file;
35          this.size = roundTo4096(len);
36          mapAndSetOffset();
37      }
38  
39      // Bundle reflection calls to get access to the given method
40      @VisibleForTesting
41      static Method getMethod(Class<?> cls, String name, Class<?>... params) {
42          Method m;
43          try {
44              m = cls.getDeclaredMethod(name, params);
45          } catch (Exception e) {
46              throw new RuntimeException(e);
47          }
48          m.setAccessible(true);
49          return m;
50      }
51  
52      // Round to next 4096 bytes
53      private static long roundTo4096(long i) {
54          return (i + 0xfffL) & ~0xfffL;
55      }
56  
57      // Given that the location and size have been set, map that location
58      // for the given length and set this.addr to the returned offset
59      private void mapAndSetOffset() {
60          try {
61              final RandomAccessFile backingFile = new RandomAccessFile(this.file, "rw");
62              backingFile.setLength(this.size);
63              final FileChannel ch = backingFile.getChannel();
64              this.addr = (Long) mmap.invoke(ch, 1, 0L, this.size);
65  
66              ch.close();
67              backingFile.close();
68          } catch (Exception e) {
69              throw new RuntimeException(e);
70          }
71      }
72  
73      // // Callers should synchronize to avoid calls in the middle of this, but
74      // // it is undesirable to synchronize w/ all access methods.
75      // public void remap(long nLen) throws Exception {
76      // unmmap.invoke(null, addr, this.size);
77      // this.size = roundTo4096(nLen);
78      // mapAndSetOffset();
79      // }
80  
81      public void close() {
82          try {
83              unmmap.invoke(null, addr, this.size);
84              if (!file.delete()) {
85                  throw new RuntimeException("could not delete " + file);
86              }
87          } catch (IllegalAccessException e) {
88              throw new RuntimeException(e);
89          } catch (IllegalArgumentException e) {
90              throw new RuntimeException(e);
91          } catch (InvocationTargetException e) {
92              throw new RuntimeException(e);
93          }
94      }
95  
96      public int getInt(long pos) {
97          return unsafe.getInt(pos + addr);
98      }
99  
100     public void putByte(long pos, byte val) {
101         unsafe.putByte(pos + addr, val);
102     }
103 
104     public void putInt(long pos, int val) {
105         unsafe.putInt(pos + addr, val);
106     }
107 
108     public void putOrderedInt(long pos, int val) {
109         unsafe.putOrderedInt(null, pos + addr, val);
110     }
111 
112     public int getIntVolatile(long pos) {
113         return unsafe.getIntVolatile(null, pos + addr);
114     }
115 
116     // May want to have offset & length within data as well, for both of these
117     public void getBytes(long pos, byte[] data, long offset, long length) {
118         unsafe.copyMemory(null, pos + addr, data, BYTE_ARRAY_OFFSET + offset, length);
119     }
120 
121     public void putBytes(long pos, byte[] data, long offset, long length) {
122         unsafe.copyMemory(data, BYTE_ARRAY_OFFSET + offset, null, pos + addr, length);
123     }
124 
125     public byte getByte(long pos) {
126         return unsafe.getByte(pos + addr);
127     }
128 
129 }