Как удалить файл из памяти, отображенной с помощью FileChannel в Java?
Я сопоставляю файл ("sample.txt") в память, используя FileChannel.map()
а затем закрытие канала с помощью fc.close()
, После этого, когда я пишу в файл, используя FileOutputStream, я получаю следующую ошибку:
java.io.FileNotFoundException: sample.txt (Запрошенная операция не может быть выполнена для файла с открытым отображенным пользователем разделом)
File f = new File("sample.txt");
RandomAccessFile raf = new RandomAccessFile(f,"rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
fc.close();
raf.close();
FileOutputStream fos = new FileOutputStream(f);
fos.write(str.getBytes());
fos.close();
Я предполагаю, что это может быть связано с тем, что файл все еще отображается в памяти даже после закрытия FileChannel
, Я прав?. Если так, как я могу "удалить" файл из памяти?(Я не могу найти какие-либо методы для этого в API). Благодарю.
Изменить: Похоже, что (добавив метод unmap) был представлен как RFE для Sun некоторое время назад: http://bugs.sun.com/view_bug.do?bug_id=4724038
12 ответов
От MappedByteBuffer
Javadoc:
Отображенный байтовый буфер и сопоставление файлов, которое он представляет, остаются действительными до тех пор, пока сам буфер не будет собран сборщиком мусора.
Попробуйте позвонить System.gc()
? Даже это только предложение для ВМ.
Можно использовать следующий статический метод:
public static void unmap(MappedByteBuffer buffer)
{
sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
cleaner.clean();
}
Но это небезопасное решение из-за следующего:
1) привести к сбоям, если кто-то использует MappedByteBuffer после unmap
2) Он опирается на детали реализации MappedByteBuffer
[WinXP,SunJDK1.6] У меня был привязанный ByteBuffer из файлового канала. После прочтения SO сообщений, наконец, удалось вызвать уборщика через отражение без каких-либо импортных пакетов sun.*. Больше нет блокировки файлов.
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer cb = null;
try {
long size = fc.size();
cb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
...do the magic...
finally {
try { fc.close(); } catch (Exception ex) { }
try { fis.close(); } catch (Exception ex) { }
closeDirectBuffer(cb);
}
private void closeDirectBuffer(ByteBuffer cb) {
if (cb==null || !cb.isDirect()) return;
// we could use this type cast and call functions without reflection code,
// but static import from sun.* package is risky for non-SUN virtual machine.
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
try {
Method cleaner = cb.getClass().getMethod("cleaner");
cleaner.setAccessible(true);
Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
clean.setAccessible(true);
clean.invoke(cleaner.invoke(cb));
} catch(Exception ex) { }
cb = null;
}
Идеи были взяты из этих постов.
* Как удалить файл из памяти, отображенной с помощью FileChannel в Java?
* Примеры принудительного освобождения нативной памяти, выделенной прямым ByteBuffer с использованием sun.misc.Unsafe?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40
Sun.misc.Cleaner Javadoc говорит:
Универсальные чистящие средства на основе фантомных ссылок. Очистители - это легкая и более надежная альтернатива доработке. Они легковесны, потому что они не созданы виртуальной машиной и, следовательно, не требуют создания вызова JNI, а также потому, что их код очистки вызывается непосредственно потоком обработчика ссылок, а не потоком финализатора. Они более надежны, потому что используют фантомные ссылки, самый слабый тип ссылочного объекта, что позволяет избежать неприятных проблем упорядочения, присущих финализации. Очиститель отслеживает референтный объект и инкапсулирует множество произвольного кода очистки. Через некоторое время после того, как GC обнаружит, что референт очистителя стал фантомно достижимым, поток обработчика ссылок запустит очиститель. Очистители также могут быть вызваны напрямую; они безопасны для потока и гарантируют, что они запускают свои блоки не более одного раза. Очистители не являются заменой для завершения. Их следует использовать только тогда, когда код очистки предельно прост и понятен. Нетривиальные очистители нецелесообразны, так как они рискуют заблокировать поток обработчика ссылок и отложить дальнейшую очистку и завершение.
Запуск System.gc() является приемлемым решением, если общий размер ваших буферов невелик, но если бы я отображал гигабайты файлов, я бы попытался реализовать это так:
((DirectBuffer) buffer).cleaner().clean()
Но! Убедитесь, что у вас нет доступа к этому буферу после очистки, иначе вы получите:
В среде выполнения Java обнаружена неустранимая ошибка: EXCEPTION_ACCESS_VIOLATION (0xc0000005) при pc=0x0000000002bcf700, pid=7592, tid=10184 Версия JRE: среда выполнения Java(TM) SE (8.0_40-b25) (сборка 1.8.0_40-) b25) Java VM: виртуальная машина Java HotSpot(TM), 64-разрядная серверная виртуальная машина (25.40-b25, смешанный режим, windows-amd64, сжатые операции). Проблемный кадр: J 85 C2 java.nio.DirectByteBuffer.get(I)B (16 байт) @ 0x0000000002bcf700 [0x0000000002bcf6c0+0x40] Не удалось записать дамп ядра. Мини-дампы не включены по умолчанию в клиентских версиях Windows. Файл отчета об ошибке с дополнительной информацией сохраняется как: C:\Users\?????\Programs\testApp\hs_err_pid7592.log Скомпилированный метод (c2) 42392 85 4 java.nio.DirectByteBuffer::get (16 байт) всего в куче [0x0000000002bcf590,0x0000000002bcf828] = перемещение 664 [0x0000000002bcf6b0,0x0000000002bcf6c0] = 16 основной код [0x0000000000bff 060000000bfc
[0x0000000002bcf760,0x0000000002bcf778] = 24 упа
[0x0000000002bcf778,0x0000000002bcf780] = 8 метаданных
[0x0000000002bcf780,0x0000000002bcf798] = 24 области данных
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 шт.
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 зависимости
[0x0000000002bcf820,0x0000000002bcf828] = 8
Удачи!
Метод, описанный в других ответах, который использует ((DirectBuffer) byteBuffer).cleaner().clean()
не работает на JDK 9+ (даже в отражающей форме) без отображения An illegal reflective access operation has occurred
предупреждение. Это перестанет работать вообще в некоторых будущих версиях JDK. к счастью sun.misc.Unsafe.invokeCleaner(ByteBuffer)
может сделать тот же самый вызов для вас без предупреждения: (из источника OpenJDK 11):
public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
if (!directBuffer.isDirect())
throw new IllegalArgumentException("buffer is non-direct");
DirectBuffer db = (DirectBuffer)directBuffer;
if (db.attachment() != null)
throw new IllegalArgumentException("duplicate or slice");
Cleaner cleaner = db.cleaner();
if (cleaner != null) {
cleaner.clean();
}
}
Быть sun.misc
класс, он будет удален в какой-то момент. Интересно все звонки, кроме этого в sun.misc.Unsafe
проксируются напрямую jdk.internal.misc.Unsafe
(Я не знаю почему invokeCleaner(ByteBuffer)
не проксируется так же, как все остальные методы - это, вероятно, случайное упущение).
Я написал следующий код, который может очищать / закрывать / отменять отображение экземпляров DirectByteBuffer/MappedByteBuffer на JDK 7/8, а также на JDK 9+, и это не дает предупреждение об отражении:
private static boolean PRE_JAVA_9 =
System.getProperty("java.specification.version","9").startsWith("1.");
private static Method cleanMethod;
private static Method attachmentMethod;
private static Object theUnsafe;
static void getCleanMethodPrivileged() {
if (PRE_JAVA_9) {
try {
cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
cleanMethod.setAccessible(true);
final Class<?> directByteBufferClass =
Class.forName("sun.nio.ch.DirectBuffer");
attachmentMethod = directByteBufferClass.getMethod("attachment");
attachmentMethod.setAccessible(true);
} catch (final Exception ex) {
}
} else {
try {
Class<?> unsafeClass;
try {
unsafeClass = Class.forName("sun.misc.Unsafe");
} catch (Exception e) {
// jdk.internal.misc.Unsafe doesn't yet have invokeCleaner(),
// but that method should be added if sun.misc.Unsafe is removed.
unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
}
cleanMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
cleanMethod.setAccessible(true);
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
theUnsafe = theUnsafeField.get(null);
} catch (final Exception ex) {
}
}
}
static {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
getCleanMethodPrivileged();
return null;
}
});
}
private static boolean closeDirectByteBufferPrivileged(
final ByteBuffer byteBuffer, final LogNode log) {
try {
if (cleanMethod == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, cleanMethod == null");
}
return false;
}
if (PRE_JAVA_9) {
if (attachmentMethod == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, attachmentMethod == null");
}
return false;
}
// Make sure duplicates and slices are not cleaned, since this can result in
// duplicate attempts to clean the same buffer, which trigger a crash with:
// "A fatal error has been detected by the Java Runtime Environment:
// EXCEPTION_ACCESS_VIOLATION"
// See: https://stackru.com/a/31592947/3950982
if (attachmentMethod.invoke(byteBuffer) != null) {
// Buffer is a duplicate or slice
return false;
}
// Invoke ((DirectBuffer) byteBuffer).cleaner().clean()
final Method cleaner = byteBuffer.getClass().getMethod("cleaner");
cleaner.setAccessible(true);
cleanMethod.invoke(cleaner.invoke(byteBuffer));
return true;
} else {
if (theUnsafe == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, theUnsafe == null");
}
return false;
}
// In JDK9+, calling the above code gives a reflection warning on stderr,
// need to call Unsafe.theUnsafe.invokeCleaner(byteBuffer) , which makes
// the same call, but does not print the reflection warning.
try {
cleanMethod.invoke(theUnsafe, byteBuffer);
return true;
} catch (final IllegalArgumentException e) {
// Buffer is a duplicate or slice
return false;
}
}
} catch (final Exception e) {
if (log != null) {
log.log("Could not unmap ByteBuffer: " + e);
}
return false;
}
}
/**
* Close a {@code DirectByteBuffer} -- in particular, will unmap a
* {@link MappedByteBuffer}.
*
* @param byteBuffer
* The {@link ByteBuffer} to close/unmap.
* @param log
* The log.
* @return True if the byteBuffer was closed/unmapped (or if the ByteBuffer
* was null or non-direct).
*/
public static boolean closeDirectByteBuffer(final ByteBuffer byteBuffer,
final Log log) {
if (byteBuffer != null && byteBuffer.isDirect()) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return closeDirectByteBufferPrivileged(byteBuffer, log);
}
});
} else {
// Nothing to unmap
return false;
}
}
Обратите внимание, что вам нужно будет добавить requires jdk.unsupported
в дескриптор вашего модуля в модульной среде выполнения на JDK 9+ (необходим для использования Unsafe
).
Ваша банка может также понадобиться RuntimePermission("accessClassInPackage.sun.misc")
, RuntimePermission("accessClassInPackage.jdk.internal.misc")
, а также ReflectPermission("suppressAccessChecks")
,
Я узнал информацию о unmap
это метод FileChannelImpl
и не доступны, так что вы можете вызвать его с помощью java отражать как:
public static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) {
if (buffer == null) {
return;
}
try {
Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class);
unmap.setAccessible(true);
unmap.invoke(channelClass, buffer);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
Чтобы обойти эту ошибку в Java, я должен был сделать следующее, что будет нормально работать для файлов малого и среднего размера:
// first open the file for random access
RandomAccessFile raf = new RandomAccessFile(file, "r");
// extract a file channel
FileChannel channel = raf.getChannel();
// you can memory-map a byte-buffer, but it keeps the file locked
//ByteBuffer buf =
// channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// or, since map locks the file... just read the whole file into memory
ByteBuffer buf = ByteBuffer.allocate((int)file.length());
int read = channel.read(buf);
// .... do something with buf
channel.force(false); // doesn't help
channel.close(); // doesn't help
channel = null; // doesn't help
buf = null; // doesn't help
raf.close(); // try to make sure that this thing is closed!!!!!
Отображенная память используется, пока она не будет освобождена сборщиком мусора.
Из документов FileChannel
Сопоставление, когда оно установлено, не зависит от файлового канала, который использовался для его создания. В частности, закрытие канала не влияет на достоверность сопоставления.
От MappedByteBuffer Java документ
Отображенный байтовый буфер и сопоставление файлов, которое он представляет, остаются действительными до тех пор, пока сам буфер не будет удален.
Поэтому я хотел бы предложить убедиться, что не осталось никаких ссылок на сопоставленный байтовый буфер, а затем запросить сборку мусора.
Забавно видеть так много рекомендаций, чтобы делать то, что в пункте 7 "Эффективной Java" определенно сказано, что не следует делать. Необходим метод завершения, такой как @Whome, и отсутствие ссылок на буфер. GC не может быть принудительным. Но это не останавливает разработчиков от попыток. Другой обходной путь, который я нашел, заключался в использовании WeakReferences с http://jan.baresovi.cz/dr/en/java.
final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
....
final WeakReference<mappedbytebuffer> bufferWeakRef = new WeakReference<mappedbytebuffer>(bb);
bb = null;
final long startTime = System.currentTimeMillis();
while(null != bufferWeakRef.get()) {
if(System.currentTimeMillis() - startTime > 10)
// give up
return;
System.gc();
Thread.yield();
}
Попробуйте https://github.com/real-logic/agrona
Его класс IOUtil имеет unmap(MappedByteBuffer)
метод, который делает именно то, что вам нужно. Это позволяет явно отменить отображение MappedByteBuffer.
Однако он использует sun.misc.Unsafe
внутренне, но это не отличается от других ответов здесь.
Я бы попробовал JNI:
#ifdef _WIN32
UnmapViewOfFile(env->GetDirectBufferAddress(buffer));
#else
munmap(env->GetDirectBufferAddress(buffer), env->GetDirectBufferCapacity(buffer));
#endif
Включите файлы: windows.h для Windows, sys/mmap.h для BSD, Linux, OSX.
Если объект сопоставленного файлового буфера может быть гарантированно пригоден для сбора мусора, вам не нужно собирать всю виртуальную машину для получения освобождения отображенной памяти буфера. Вы можете вызвать System.runFinalization() . Это вызовет метод finalize() для объекта буфера сопоставленного файла (если на него нет ссылок в потоках приложения), который освободит сопоставленную память.
Правильным решением здесь является использование try-with-resources.
Это позволяет создавать канал и другие ресурсы, которые можно ограничить блоком. Как только блок выходит, Канал и другие ресурсы исчезают и впоследствии не могут использоваться (поскольку ничто не имеет на них ссылку).
Отображение памяти все еще не будет отменено до следующего запуска GC, но, по крайней мере, нет никаких висящих ссылок на него.