Проблема Java ClassLoader или ошибка параллелизма?

После того, как приложение WebLogic работало нормально в течение нескольких недель, я внезапно получаю исключение:

<Oct 25, 2014 9:31:11 PM EDT> <Error> <HTTP> <BEA-101020>
    <[ServletContext@60724164[app:whatever3000 module:whatever3000.war path:
    spec-version:2.5]] Servlet failed with Exception
    java.lang.ExceptionInInitializerError

после чего приложение полностью перестает работать с NoClassDefFoundError, пока сервер приложений не будет перезапущен.

Полная трассировка стека показывает, что источником проблемы является ConcurrentModificationException в статическом инициализаторе.

В частности, эквивалентный / минимизированный код выглядит следующим образом:

package a;
import b;

public class Whatever {
    void doIt()
    {   
        Password p = new Password();
    }   
}

package b;

public final class Password implements Serializable
{
    private static final int PARAM1 = CommonStuff.someStaticMethod();
    ... 
}

import java.util.Properties;

public class CommonStuff
{
    private static Properties prp = new Properties();

    static {
        CommonStuff.load();
    }   

    public static void load()
    {   
        prp.putAll(System.getProperties()); <---FAIL

Это источник исключения:

java.util.ConcurrentModificationException
        at java.util.Hashtable$Enumerator.next(Hashtable.java:1017)
        at java.util.Hashtable.putAll(Hashtable.java:469)
        at b.CommonStuff.load(CommonStuff.java:55)
        at b.CommonStuff.<clinit>(CommonStuff.java:77)
        at b.Password.<clinit>(Password.java:44)
        at a.doIt(Whatever.java:99)

Таким образом, кажется, что в какой-то момент во время выполнения приложения WebLogic решил перезагрузить классы из package b, но при запуске статического блока обнаруживает, что объект Properties был изменен.

Я не знаю, вызывается ли он одновременно или несколько раз. Возможно, объект Properties является исходным экземпляром, созданным, когда приложение загружается свежим в первый раз, и при перезагрузке класса CommonStuff снова пытается вызвать putAll().

Помогло бы, если бы я:

private static Properties prp = null;

static {
    CommonStuff.prp = new Properties();
    CommonStuff.load();
}

Я не могу просто пробовать что-то вслепую, потому что это производственное приложение в огромной компании. Итак, я пытаюсь понять, где я ошибаюсь и как инициализировать эти переменные, когда классы перезагружаются посреди ночи.

Любые идеи?

Может ли это быть проблемой WebLogic ClassLoader?


person squarewav    schedule 28.10.2014    source источник


Ответы (1)


Классы / экземпляры не могут получить доступ к некоторым членам класса до инициализации этого класса. Из-за этого никто не может получить доступ к вновь созданному prp до возврата статического конструктора. Перемещение prp инициализатора внутрь блока static {} не имеет значения. Также "старый" prp в "старом" CommonStuff и "новый" prp никак не связаны (потому что "старый" и "новый" CommonStuff - разные классы для JVM). Все это делает возможность одновременных модификаций prp выглядеть довольно странно.

Думаю, причина в другом. Обратите внимание на первую строку трассировки стека: исключение выдается Enumerator из Hashtable. Вот код метода putAll (как и в JDK 8, вероятно, не менялся много лет):

for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
    put(e.getKey(), e.getValue());

Вот Enumerator, который вызывает исключение - и это не prp, это Enumerator аргумента.

Таким образом, исключение связано не с prp, а с Map, возвращенным System.getProperties(). Причина в том, что перебор карты свойств системы не является потокобезопасным. Кажется, что другой поток модифицирует его в то же время.

Вам нужно инициализировать prp иначе. Я думаю, что clone() - самый простой способ.

person Stanislav Lukyanov    schedule 29.10.2014
comment
Похоже, это, вероятно, причина. У кого-то другого независимо был такой же диагноз. Я добавил clone (), и в ближайшие несколько недель мы увидим, сработает ли он. Спасибо. - person squarewav; 31.10.2014