Оказывается, мапирование в Java не работает в принципе: проголосуйте за ошибку, пожалуйста
Недавно выяснил, что мапированием в текущей версии Java (MappedByteBuffer) пользоваться нежелательно, по сути, почти ни при каких условиях. Завел соответствующий баг:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6521677
Аналогичную ошибку эту заметили еще в 2003 году: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4938372
так что сотрудники Sun справедливо объявили мой баг (6521677) дубликатом этого.
Если коротко и по-русски, то ситуация довольно скандальная. Берем вот такой вот циклик:
Т.е. попросту разбиваем файл на блоки по size байтов и последовательно их мапируем - ничего более! И с хорошей такой вероятностью, под Windows, все современные версии JVM катастрофически умирают в потоке сборки мусора с заявлением "Cleaner terminated abnormally":
Полный тест, который приведен в тексте бага, на моем компьтере (вероятно, и на других) приводит к стабильному вылетанию.
Многое зависит от n и size. Скажем, если мы пытаемся мапировать блоки по 32K (size=32768), то система выдерживает до нескольких сотен блоков (n). А вот если блок 2 мегабайта, то у меня система рушилась уже на n=10! Этого не очень легко добиться (я крутил гораздо более сложный тест несколько минут), но даже в приведенном выше цикле система упадет, если поставить n=200 (файл 400 MB), а через каждые 20-30 блоков (if (i % 30 == 0) ...) выполнять System.gc() - который, как легко убедиться, размапирует все замапированные раньше блоки.
Иначе говоря, имеется некий тяжелый баг: размапирование в потоке сборки мусора может войти в конфликт с мапированием в обычном потоке. Может быть, все будет работать стабильно, если замапировать файл целиком, поработать, размапировать и более никогда не пытаться его мапировать. Но это "может быть", вообще-то, ниоткуда не следует (кроме рекомендаций программистов Sun, которые выглядят, скорее, как отписка). Если JVM может упасть (причем катастрофически и невосстановимо!) после нескольких десятков мапирований непересекающихся 2-мегабайтных кусков файла, то почему бы ей не упасть - пусть с меньшей вероятностью - после одного-единственного мапирования?
Вывод мой печален: заявленным методом FileChannel.map в серьезных, долгоиграющих приложениях пользоваться нельзя категорически. Пока Sun не исправит ошибку.
А мне как раз мапирование очень нужно.
Я делаю поддержку сверхбольших массивов (2- и 3-мерных матриц), для которых 2-гигабайтное ограничение, накладываемое как массивами Java, так и ByteBuffer API, слишком жесткое. Причем я не могу поручиться, что доступ к этим массивам будет исключительно последовательным, поэтому обычные операции блочного чтения/записи могут оказаться чрезвычайно неэффективными. Я хотел применить мапирование большого файла большими блоками (16 или даже 256 мегабайт), при необходимости мапируя новые блоки и предоставляя сборщику мусора Java размапировать старые. Тогда с оптимальной подкачкой фрагментов файла будет разбираться супер-интеллектуальный алгоритм подкачки страниц OS, имеющий в своем распоряжении всю оперативную память системы. И вот - кажется, наступил мощный облом.
Так что большая просьба: пожалуйста, не пожалейте 10 минут, зарегистрируйтесь и проголосуйте за ошибку
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4938372
Если наберется несколько десятков голосов, ошибка может попасть в Top25 - глядишь, тогда программисты Sun таки приложат усилия и исправят ошибку хотя бы к выпуску Java 1.7.
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6521677
Аналогичную ошибку эту заметили еще в 2003 году: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4938372
так что сотрудники Sun справедливо объявили мой баг (6521677) дубликатом этого.
Если коротко и по-русски, то ситуация довольно скандальная. Берем вот такой вот циклик:
(вначале создаем файл f размера n*size и аккуратно закрываем его)
raf = new RandomAccessFile(f, "rw");
for (int i = 0; i < n; i++) {
ByteBuffer bb = raf.getChannel().map(
FileChannel.MapMode.READ_WRITE, i * size, size);
for (int j = 0; j < bb.limit(); j++)
ну, скажем, bb.put(j, (byte)i);
}
raf.close();
Т.е. попросту разбиваем файл на блоки по size байтов и последовательно их мапируем - ничего более! И с хорошей такой вероятностью, под Windows, все современные версии JVM катастрофически умирают в потоке сборки мусора с заявлением "Cleaner terminated abnormally":
java.lang.Error: Cleaner terminated abnormally
at sun.misc.Cleaner$1.run(Cleaner.java:130)
at java.security.AccessController.doPrivileged(Native Method)
at sun.misc.Cleaner.clean(Cleaner.java:127)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:124)
Caused by: java.io.IOException: The process cannot access the file because another process has locked a portion of the file
at sun.nio.ch.FileChannelImpl.unmap0(Native Method)
at sun.nio.ch.FileChannelImpl.access$100(FileChannelImpl.java:32)
at sun.nio.ch.FileChannelImpl$Unmapper.run(FileChannelImpl.java:680)
at sun.misc.Cleaner.clean(Cleaner.java:125)
... 1 more
Полный тест, который приведен в тексте бага, на моем компьтере (вероятно, и на других) приводит к стабильному вылетанию.
Многое зависит от n и size. Скажем, если мы пытаемся мапировать блоки по 32K (size=32768), то система выдерживает до нескольких сотен блоков (n). А вот если блок 2 мегабайта, то у меня система рушилась уже на n=10! Этого не очень легко добиться (я крутил гораздо более сложный тест несколько минут), но даже в приведенном выше цикле система упадет, если поставить n=200 (файл 400 MB), а через каждые 20-30 блоков (if (i % 30 == 0) ...) выполнять System.gc() - который, как легко убедиться, размапирует все замапированные раньше блоки.
Иначе говоря, имеется некий тяжелый баг: размапирование в потоке сборки мусора может войти в конфликт с мапированием в обычном потоке. Может быть, все будет работать стабильно, если замапировать файл целиком, поработать, размапировать и более никогда не пытаться его мапировать. Но это "может быть", вообще-то, ниоткуда не следует (кроме рекомендаций программистов Sun, которые выглядят, скорее, как отписка). Если JVM может упасть (причем катастрофически и невосстановимо!) после нескольких десятков мапирований непересекающихся 2-мегабайтных кусков файла, то почему бы ей не упасть - пусть с меньшей вероятностью - после одного-единственного мапирования?
Вывод мой печален: заявленным методом FileChannel.map в серьезных, долгоиграющих приложениях пользоваться нельзя категорически. Пока Sun не исправит ошибку.
А мне как раз мапирование очень нужно.
Я делаю поддержку сверхбольших массивов (2- и 3-мерных матриц), для которых 2-гигабайтное ограничение, накладываемое как массивами Java, так и ByteBuffer API, слишком жесткое. Причем я не могу поручиться, что доступ к этим массивам будет исключительно последовательным, поэтому обычные операции блочного чтения/записи могут оказаться чрезвычайно неэффективными. Я хотел применить мапирование большого файла большими блоками (16 или даже 256 мегабайт), при необходимости мапируя новые блоки и предоставляя сборщику мусора Java размапировать старые. Тогда с оптимальной подкачкой фрагментов файла будет разбираться супер-интеллектуальный алгоритм подкачки страниц OS, имеющий в своем распоряжении всю оперативную память системы. И вот - кажется, наступил мощный облом.
Так что большая просьба: пожалуйста, не пожалейте 10 минут, зарегистрируйтесь и проголосуйте за ошибку
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4938372
Если наберется несколько десятков голосов, ошибка может попасть в Top25 - глядишь, тогда программисты Sun таки приложат усилия и исправят ошибку хотя бы к выпуску Java 1.7.
