Подтвердить что ты не робот

Усекать файл с отображением памяти

Я использую IO с отображением памяти для индексного файла, но проблема в том, что я не могу изменить размер файла, если он в основном пуст.

Где-то раньше:

MappedByteBuffer map = raf.getChannel().map(MapMode.READ_WRITE, 0, 1 << 30);
raf.close();
// use map
map.force();
map = null;

Изменение размера:

for (int c = 0; c < 100; c++) {
    RandomAccessFile raf = new RandomAccessFile(indexFile, "rw");
    try {
        raf.setLength(newLen);
        if (c > 0) LOG.warn("used " + c + " iterations to close mapped byte buffer");
        return;
    } catch (Exception e) {
        System.gc();
        Thread.sleep(10);
        System.runFinalization();
        Thread.sleep(10);
    } finally {
        raf.close();
    }
}

При использовании 32-разрядной версии Windows или Linux у меня часто возникает проблема с отображением, но в 64-битной производственной среде Linux все работает без предупреждений, но файл сохраняет исходный размер.

Может ли кто-нибудь объяснить, почему это происходит и/или как решить проблему?

4b9b3361

Ответ 1

Ваша проблема в том, что вы используете ненадежный метод для закрытия отображаемого байт-буфера (сто вызовов System.gc() и System.runFinalization() не гарантируют ничего). К сожалению, в Java API нет надежного метода, но на Sun JVM (и, возможно, на некоторых других) вы можете использовать следующий код:

public void unmapMmaped(ByteBuffer buffer) {
  if (buffer instanceof sun.nio.ch.DirectBuffer) {
    sun.misc.Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner();
    cleaner.clean();
  }
}

Конечно, это зависит от JVM, и вы должны быть готовы исправить свой код, если Sun когда-либо решит изменить sun.nio.ch.DirectBuffer или sun.misc.Cleaner несовместимым образом (но на самом деле я не верю, что это когда-либо произойдет).

Ответ 2

Это просто дополнение к предыдущему ответу, что совершенно правильно.

JDK 1.7 жалуется на использование sun.misc.Cleaner, говоря, что классы в этом пространстве имен не являются официальной частью JDK и могут исчезнуть в будущем. Однако по состоянию на 1.7 они все еще присутствуют.

Если метод .clean() недоступен, то использование System.gc() может использоваться как метод возврата, однако это должно быть признано "хаком", поэтому следует использовать осторожность.

В то время как System.gc() не может принудительно закрывать незаписанное сопоставление, на практике это часто вызывает очистку. Опыт 32-разрядной Linux (и Solaris) показывает, что буферы освобождаются во время каждого теста во время первого или второго вызова System.gc(). Однако поведение в Windows отличается. В большинстве случаев все сопоставления освобождаются к концу второго вызова System.gc(), но иногда для этого требуется 3 вызова. Есть еще случаи, когда требуется больше вызовов, при этом требуется большее количество вызовов, уменьшающихся по частоте. Это может быть обманчиво, поскольку тесты могут указывать на то, что 4 вызова - это все, что требуется, только чтобы он не срабатывал через месяц. 5 вызовов могут казаться адекватными, только для того, чтобы привести к сбою через 6 месяцев.

Тестирование, чтобы узнать, была ли выпущена карта, может быть сделано с помощью блока try/catch вокруг FileChannel.truncate(), с циклом, чтобы повторно попытаться выполнить операцию при сбое. Цикл не может быть бесконечным, так как существуют патологические случаи, когда конкретная конфигурация кучи приведет сборщик мусора, чтобы никогда не очищать карту. Однако цикл около 10 будет охватывать почти все случаи. Если объект не ушел по этому пункту, то он никуда не денется, и приложение придется сдаться. Это может показаться неадекватным, но на практике это крайне маловероятно, и это будет проблемой только для JVM, которая не поддерживает очистителей.