@@ -51,11 +51,21 @@ This file is part of the iText (R) project.
51
51
import java .io .ObjectInputStream ;
52
52
import java .io .ObjectOutputStream ;
53
53
import java .io .Serializable ;
54
+ import java .lang .invoke .MethodHandle ;
54
55
import java .lang .reflect .Method ;
55
56
import java .nio .BufferUnderflowException ;
56
57
import java .security .AccessController ;
57
58
import java .security .PrivilegedAction ;
58
59
60
+ import java .nio .ByteBuffer ;
61
+ import java .util .Objects ;
62
+ import java .lang .reflect .Field ;
63
+
64
+ import static java .lang .invoke .MethodHandles .Lookup ;
65
+ import static java .lang .invoke .MethodHandles .lookup ;
66
+ import static java .lang .invoke .MethodType .methodType ;
67
+
68
+
59
69
/**
60
70
* A RandomAccessSource that is based on an underlying {@link java.nio.ByteBuffer}. This class takes steps to ensure that the byte buffer
61
71
* is completely freed from memory during {@link ByteBufferRandomAccessSource#close()}
@@ -71,6 +81,7 @@ class ByteBufferRandomAccessSource implements IRandomAccessSource, Serializable
71
81
72
82
/**
73
83
* Constructs a new {@link ByteBufferRandomAccessSource} based on the specified ByteBuffer
84
+ *
74
85
* @param byteBuffer the buffer to use as the backing store
75
86
*/
76
87
public ByteBufferRandomAccessSource (java .nio .ByteBuffer byteBuffer ) {
@@ -81,6 +92,7 @@ public ByteBufferRandomAccessSource(java.nio.ByteBuffer byteBuffer) {
81
92
* {@inheritDoc}
82
93
* <p>
83
94
* Note: Because ByteBuffers don't support long indexing, the position must be a valid positive int
95
+ *
84
96
* @param position the position to read the byte from - must be less than Integer.MAX_VALUE
85
97
*/
86
98
public int get (long position ) throws java .io .IOException {
@@ -90,7 +102,7 @@ public int get(long position) throws java.io.IOException {
90
102
91
103
if (position >= byteBuffer .limit ())
92
104
return -1 ;
93
- byte b = byteBuffer .get ((int )position );
105
+ byte b = byteBuffer .get ((int ) position );
94
106
return b & 0xff ;
95
107
} catch (BufferUnderflowException e ) {
96
108
return -1 ; // EOF
@@ -101,6 +113,7 @@ public int get(long position) throws java.io.IOException {
101
113
* {@inheritDoc}
102
114
* <p>
103
115
* Note: Because ByteBuffers don't support long indexing, the position must be a valid positive int
116
+ *
104
117
* @param position the position to read the byte from - must be less than Integer.MAX_VALUE
105
118
*/
106
119
public int get (long position , byte [] bytes , int off , int len ) throws java .io .IOException {
@@ -111,7 +124,7 @@ public int get(long position, byte[] bytes, int off, int len) throws java.io.IOE
111
124
return -1 ;
112
125
113
126
// Not thread safe!
114
- byteBuffer .position ((int )position );
127
+ byteBuffer .position ((int ) position );
115
128
int bytesFromThisBuffer = Math .min (len , byteBuffer .remaining ());
116
129
byteBuffer .get (bytes , off , bytesFromThisBuffer );
117
130
@@ -134,8 +147,35 @@ public void close() throws java.io.IOException {
134
147
clean (byteBuffer );
135
148
}
136
149
150
+
151
+ /**
152
+ * <code>true</code>, if this platform supports unmapping mmapped files.
153
+ */
154
+ public static final boolean UNMAP_SUPPORTED ;
155
+
156
+ /**
157
+ * Reference to a BufferCleaner that does unmapping; {@code null} if not supported.
158
+ */
159
+ private static final BufferCleaner CLEANER ;
160
+
161
+ static {
162
+ final Object hack = AccessController .doPrivileged (new PrivilegedAction <Object >() {
163
+ public Object run () {
164
+ return unmapHackImpl ();
165
+ }
166
+ });
167
+ if (hack instanceof BufferCleaner ) {
168
+ CLEANER = (BufferCleaner ) hack ;
169
+ UNMAP_SUPPORTED = true ;
170
+ } else {
171
+ CLEANER = null ;
172
+ UNMAP_SUPPORTED = false ;
173
+ }
174
+ }
175
+
137
176
/**
138
177
* invokes the clean method on the ByteBuffer's cleaner
178
+ *
139
179
* @param buffer ByteBuffer
140
180
* @return boolean true on success
141
181
*/
@@ -147,11 +187,17 @@ private static boolean clean(final java.nio.ByteBuffer buffer) {
147
187
public Boolean run () {
148
188
Boolean success = Boolean .FALSE ;
149
189
try {
150
- Method getCleanerMethod = buffer .getClass ().getMethod ("cleaner" , (Class <?>[]) null );
151
- getCleanerMethod .setAccessible (true );
152
- Object cleaner = getCleanerMethod .invoke (buffer , (Object []) null );
153
- Method clean = cleaner .getClass ().getMethod ("clean" , (Class <?>[]) null );
154
- clean .invoke (cleaner , (Object []) null );
190
+ // java 9
191
+ if (UNMAP_SUPPORTED )
192
+ CLEANER .freeBuffer (buffer .toString (), buffer );
193
+ // java 8 and lower
194
+ else {
195
+ Method getCleanerMethod = buffer .getClass ().getMethod ("cleaner" , (Class <?>[]) null );
196
+ getCleanerMethod .setAccessible (true );
197
+ Object cleaner = getCleanerMethod .invoke (buffer , (Object []) null );
198
+ Method clean = cleaner .getClass ().getMethod ("clean" , (Class <?>[]) null );
199
+ clean .invoke (cleaner , (Object []) null );
200
+ }
155
201
success = Boolean .TRUE ;
156
202
} catch (Exception e ) {
157
203
// This really is a show stopper on windows
@@ -174,12 +220,80 @@ private void writeObject(ObjectOutputStream out) throws IOException {
174
220
out .defaultWriteObject ();
175
221
}
176
222
177
- private void readObject (ObjectInputStream in ) throws IOException , ClassNotFoundException
178
- {
223
+ private void readObject (ObjectInputStream in ) throws IOException , ClassNotFoundException {
179
224
in .defaultReadObject ();
180
225
if (bufferMirror != null ) {
181
226
byteBuffer = java .nio .ByteBuffer .wrap (bufferMirror );
182
227
bufferMirror = null ;
183
228
}
184
229
}
230
+
231
+ /*
232
+ * Licensed to the Apache Software Foundation (ASF) under one or more
233
+ * contributor license agreements. See the NOTICE file distributed with
234
+ * this work for additional information regarding copyright ownership.
235
+ * The ASF licenses this file to You under the Apache License, Version 2.0
236
+ * (the "License"); you may not use this file except in compliance with
237
+ * the License. You may obtain a copy of the License at
238
+ *
239
+ * http://www.apache.org/licenses/LICENSE-2.0
240
+ *
241
+ * Unless required by applicable law or agreed to in writing, software
242
+ * distributed under the License is distributed on an "AS IS" BASIS,
243
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
244
+ * See the License for the specific language governing permissions and
245
+ * limitations under the License.
246
+ */
247
+
248
+ private static class BufferCleaner {
249
+ Class <?> unmappableBufferClass ;
250
+ final MethodHandle unmapper ;
251
+
252
+ BufferCleaner (final Class <?> unmappableBufferClass , final MethodHandle unmapper ) {
253
+ this .unmappableBufferClass = unmappableBufferClass ;
254
+ this .unmapper = unmapper ;
255
+ }
256
+
257
+ void freeBuffer (String resourceDescription , final ByteBuffer buffer ) throws IOException {
258
+ assert Objects .equals (methodType (void .class , ByteBuffer .class ), unmapper .type ());
259
+ if (!buffer .isDirect ()) {
260
+ throw new IllegalArgumentException ("unmapping only works with direct buffers" );
261
+ }
262
+ if (!unmappableBufferClass .isInstance (buffer )) {
263
+ throw new IllegalArgumentException ("buffer is not an instance of " + unmappableBufferClass .getName ());
264
+ }
265
+ final Throwable error = AccessController .doPrivileged (new PrivilegedAction <Throwable >() {
266
+ public Throwable run () {
267
+ try {
268
+ unmapper .invokeExact (buffer );
269
+ return null ;
270
+ } catch (Throwable t ) {
271
+ return t ;
272
+ }
273
+ }
274
+ });
275
+ if (error != null ) {
276
+ throw new IOException ("Unable to unmap the mapped buffer: " + resourceDescription , error );
277
+ }
278
+ }
279
+ }
280
+
281
+ private static Object unmapHackImpl () {
282
+ final Lookup lookup = lookup ();
283
+ try {
284
+ // *** sun.misc.Unsafe unmapping (Java 9+) ***
285
+ final Class <?> unsafeClass = Class .forName ("sun.misc.Unsafe" );
286
+ // first check if Unsafe has the right method, otherwise we can give up
287
+ // without doing any security critical stuff:
288
+ final MethodHandle unmapper = lookup .findVirtual (unsafeClass , "invokeCleaner" ,
289
+ methodType (void .class , ByteBuffer .class ));
290
+ // fetch the unsafe instance and bind it to the virtual MH:
291
+ final Field f = unsafeClass .getDeclaredField ("theUnsafe" );
292
+ f .setAccessible (true );
293
+ final Object theUnsafe = f .get (null );
294
+ return new BufferCleaner (ByteBuffer .class , unmapper .bindTo (theUnsafe ));
295
+ } catch (Exception e ) {
296
+ return e .getMessage ();
297
+ }
298
+ }
185
299
}
0 commit comments