Сжатие огромной папки с использованием ZipFileSystem приводит к OutOfMemoryError

java.nio Пакет имеет прекрасный способ обработки zip-файлов, рассматривая их как файловые системы. Это позволяет нам обращаться с содержимым почтового файла как с обычными файлами. Таким образом, архивирование всей папки может быть достигнуто простым использованием Files.copy скопировать все файлы в zip-файл. Поскольку подпапки также должны быть скопированы, нам нужен посетитель:

 private static class CopyFileVisitor extends SimpleFileVisitor<Path> {
    private final Path targetPath;
    private Path sourcePath = null;
    public CopyFileVisitor(Path targetPath) {
        this.targetPath = targetPath;
    }

    @Override
    public FileVisitResult preVisitDirectory(final Path dir,
    final BasicFileAttributes attrs) throws IOException {
        if (sourcePath == null) {
            sourcePath = dir;
        } else {
        Files.createDirectories(targetPath.resolve(sourcePath
                    .relativize(dir).toString()));
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(final Path file,
    final BasicFileAttributes attrs) throws IOException {
    Files.copy(file,
        targetPath.resolve(sourcePath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
    return FileVisitResult.CONTINUE;
    }
}

Это простой рекурсивный посетитель. Он используется для рекурсивного копирования каталога. Однако с ZipFileSystemМы также можем использовать его для копирования каталога в zip-файл, например так:

public static void zipFolder(Path zipFile, Path sourceDir) throws ZipException, IOException
{
    // Initialize the Zip Filesystem and get its root
    Map<String, String> env = new HashMap<>();
    env.put("create", "true");
    URI uri = URI.create("jar:" + zipFile.toUri());       
    FileSystem fileSystem = FileSystems.newFileSystem(uri, env);
    Iterable<Path> roots = fileSystem.getRootDirectories();
    Path root = roots.iterator().next();

    // Simply copy the directory into the root of the zip file system
    Files.walkFileTree(sourceDir, new CopyFileVisitor(root));
}

Это то, что я называю элегантным способом архивирования целой папки. Однако при использовании этого метода на огромной папке (около 3 ГБ) я получаю OutOfMemoryError (куча места). При использовании обычной библиотеки обработки zip эта ошибка не возникает. Таким образом, кажется, что путь ZipFileSystem обрабатывает копию очень неэффективно: слишком много файлов для записи хранится в памяти, поэтому OutOfMemoryError происходит.

Почему это так? Использует ZipFileSystem вообще считается неэффективным (с точки зрения потребления памяти) или я здесь что-то не так делаю?

2 ответа

Решение

Я посмотрел на ZipFileSystem.java и считаю, что нашел источник потребления памяти. По умолчанию реализация использует ByteArrayOutputStream в качестве буфера для сжатия файлов, что означает, что он ограничен объемом памяти, выделенным для JVM.

Существует (недокументированная) переменная среды, которую мы можем использовать, чтобы реализация использовала временные файлы ("useTempFile"). Это работает так:

Map<String, Object> env = new HashMap<>();
env.put("create", "true");
env.put("useTempFile", Boolean.TRUE);

Более подробная информация здесь: http://www.docjar.com/html/api/com/sun/nio/zipfs/ZipFileSystem.java.html, интересные строки - 96, 1358 и 1362.

Вы должны подготовить JVM, чтобы позволить этим объемам памяти с -Xms {memory} -Xmx {memory},

Я рекомендую вам проверить каталог, рассчитывающий дисковое пространство, и установить ограничение: 1 ГБ использовать файловую систему памяти, более 1 ГБ использовать файловую систему диска.

Еще одна вещь, проверьте параллелизм метода, вам не понравится более 1 потока, архивирующего 3Gb файлов

Другие вопросы по тегам