null

Падение jvm при развертывании jsf-портлетов на Liferay

Довольно часто при развертывании JSF-портлетов на Liferay Portal, развернутый на сервере приложений GlassFish, сервер падает. Это доставляет немалый дискомфорт ввиду того, что в процессе разработки переразвертывать приложение приходится достаточно часто. Подключившись к отладчику, можно перезагружать отдельные классы с помощью HotSwap при условии, что структура класса не изменена. Т.е. если добавлен метод, то придется полностью пересобирать и переразвертывать приложение.
 
Анализ многочисленных логов падения виртуальной машины показал следующую причину падения.
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=***, pid=***, tid=***
#
 
При этом все разнообразие стэков вызовов имеют примерно одинаковый конец вроде этого:
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [libc.so.1+0x3c56e]  memcpy+0x15e
C  [libzip.so+0x2ac5]  ZIP_GetEntry+0xb9
C  [libzip.so+0x2ebb]  Java_java_util_zip_ZipFile_getEntry+0x93
J  java.util.zip.ZipFile.getEntry(J[BZ)J
J  java.util.zip.ZipFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
j  java.net.URLClassLoader$3$1.run()Ljava/lang/Object;+1
Как видно, падение происходит на вызове memcpy. 
 
При открытии архива происходит отображение в память:
mappedAddr = mmap64(0, zip->mlen, PROT_READ, MAP_SHARED, zip->zfd, (off64_t) offset);
zip->maddr = (mappedAddr == (void*) MAP_FAILED) ? NULL : (unsigned char*)mappedAddr;
Соответственно при чтении какой либо записи происходит что-то вроде
if (zip->usemmap) {
    cen = (char*) zip->maddr + zc->cenpos - zip->offset;
}
...
memcpy(ze->name, cen + CENHDR, nlen);
Как раз здесь и могла произойти та самая неприятность, если, например, файл, отображенный в память, был изменен. Но мы ведь не изменяем .war файлы. Мы кидаем их в каталог авторазвертывания, откуда они удаляются после развертывания. Но тут все дело в том, что мы все время кидаем архивы с одним и тем же именем, а структуры, представляющие открытые архивы, помещаются в кеш.
zip = ZIP_Put_In_Cache0(path, zfd, &msg, lastModified, usemmap);
При развертывании нового архива, вместо того, чтобы отобразить в память новый файл, подхватывается запись из кеша
zip = ZIP_Get_From_Cache(path, &msg, lastModified);
Удаляются файлы из кеша при вызове функции close(), но по какой то причине, что и является багом, где-то наверху закрытие не происходит.
 
К счастью, в коде предусмотрена возможность отключения отображения в память как с помощью директив препроцессора, так и с помощью булевого аргумента в функции открытия.  
private static native long open(String name, int mode, long lastModified,
                                    boolean usemmap) throws IOException;
И уже в java-коде класса ZipFile можно увидеть, как снаружи установить нужное значение передаваемого аргумента.
static {
    // A system prpperty to disable mmap use to avoid vm crash when
    // in-use zip file is accidently overwritten by others.
    String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping");
    usemmap = (prop == null ||
          !(prop.length() == 0 || prop.equalsIgnoreCase("true")));
}
После добавления данной опции жизнь стала лучше
<jvm-options>-Dsun.zip.disableMemoryMapping=true</jvm-options>

 

Назад