Ответ 1 правильный. Реализация java.util.zip. * Виновата.
Если вы замените файл zip / jar, который в настоящее время Java-программа «открыла» (кэшировал объект ZipFile / JarFile), он будет использовать данные кэшированного оглавления (TOC), прочитанные из исходного файла, и попытается использовать это для распаковки данных в замененном файле. Код инфляции не является надежным и сразу выйдет из строя при представлении неверных данных.
Обычные программы Unix сохраняют файлы открытыми во время работы с ними. Если вы перезаписали файл, программа, использующая его, все равно будет иметь доступ к открытому оригиналу благодаря дескриптору открытого файла.
Реализация OpenJDK java.util.zip. * Решила не оставлять файловые дескрипторы открытыми для файлов zip / jar. Одной из причин этого может быть то, что Java часто вызывается с сотнями jar-файлов в пути к классам, и дизайнеры не хотели использовать сотни файловых дескрипторов только для jar-файлов, не оставляя ни одной для самой программы. Поэтому они закрывают файловые дескрипторы, как только прочитают оглавление jar / zip, и окончательно теряют доступ к исходному файлу jar / zip, если его содержимое изменяется.
По какой-то причине ZipFile не сообщает или не может сказать, что файл zip / jar изменился. Если это произойдет, он может перечитать оглавление или выдать какую-либо ошибку, если это невозможно.
Кроме того, даже если оглавление остается действительным, проблема заключается в том, что инфлятор дает сбой при сбое данных. Что, если оглавление ZIP было допустимым, но поток дефлированных данных был преднамеренно неправильным?
Вот тестовая программа, которая доказывает, что java.util.zip. * Не держит файловые дескрипторы открытыми для файлов zip / jar и не обнаруживает, что файл zip изменился.
import java.util.zip.*;
import java.io.*;
public class ZipCrashTest {
public static void main(String args[]) throws Exception {
// create some test data
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) sb.append("Hello, World\n");
final byte[] data = sb.toString().getBytes();
// create a zip file
try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream("test1.zip"))) {
zo.putNextEntry(new ZipEntry("world.txt")); zo.write(data, 0, data.length); zo.closeEntry();
zo.putNextEntry(new ZipEntry("hello.txt")); zo.write(data, 0, data.length); zo.closeEntry();
}
// create a second valid zip file, but with different contents
try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream("test2.zip"))) {
zo.putNextEntry(new ZipEntry("hello.txt")); zo.write(data, 0, data.length); zo.closeEntry();
zo.putNextEntry(new ZipEntry("world.txt")); zo.write(data, 0, data.length); zo.closeEntry();
}
// open the zip file
final ZipFile zf = new ZipFile("test1.zip");
// read the first file from it
try (InputStream is = zf.getInputStream(zf.getEntry("hello.txt"))) {
while (is.read() != -1) { /* do nothing with the data */ }
}
// replace the contents of test1.zip with the different-but-still-valid test2.zip
Runtime.getRuntime().exec("cp test2.zip test1.zip");
// read another entry from test1.zip: it does not detect that the file has changed.
// the program will crash here
try (InputStream is = zf.getInputStream(zf.getEntry("world.txt"))) {
while (is.read() != -1) { /* do nothing */ }
}
}
}
Запуск этой программы должен вызвать сбой JVM:
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGBUS (0x7) at pc=0x00007fb0fbbeef72, pid=4140, tid=140398238095104
...
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [libzip.so+0x4f72] Java_java_util_zip_ZipFile_getZipMessage+0x1132
C [libzip.so+0x5d7f] ZIP_GetEntry+0xcf
C [libzip.so+0x3904] Java_java_util_zip_ZipFile_getEntry+0xb4
j java.util.zip.ZipFile.getEntry(J[BZ)J+0
j java.util.zip.ZipFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;+38
j ZipCrashTest.main([Ljava/lang/String;)V+476
Основная ошибка, поданная в JVM для этого: JDK-4425695: при обновлении файлов JAR происходит сбой запущенных программ .
RFE 6929479: добавление системного свойства sun.zip.disableMemoryMapping для отключения использования mmap в ZipFile реализует системное свойство в JDK7 - sun.zip.disableMemoryMapping
- которое можно использовать в качестве временного решения.