Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Утечка памяти в jgroups при рестарте сервера? Связано с потоками 
:(
    Опции темы
lamao
Дата 1.11.2012, 13:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



Сервер - томкат. В приложении одним из артефактов используется jgroups (вроде 2.10). С добавлением этого артефакта стал намного чаще падать сервер при редеплое и перезапуске. Для установлении причины был использован Plumbr. Если не учитывать проблеммы самого томката, то единственной причиной есть 

Код

The leaking classloader is being held
in org.jgroups.util.Util$1
in groups of java.lang.ThreadGroup


А именно кусок кода в org.jgroups.util.Util
Код

private static ThreadGroup GLOBAL_GROUP=new ThreadGroup("JGroups") {
        public void uncaughtException(Thread t, Throwable e) {
            LogFactory.getLog("org.jgroups").error("uncaught exception in " + t + " (thread group=" + GLOBAL_GROUP + " )", e);
        }
    };


Для решение проблеммы был написан слушатель JGroupsCleanerListener, который, во-первых, зануляет статическую переменную, во-вторых, убивает все потоки в группе JGroups и удаляет ее.
Код

public class JGroupsCleanerListener implements ServletContextListener {

    private final static Logger logger = LoggerFactory.getLogger(JGroupsCleanerListener.class);
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // nop        
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        try {
            Field field = Util.class.getDeclaredField("GLOBAL_GROUP");
            field.setAccessible(true);
            field.set(null, null);
            logger.info("Util.getGlobalThreadGroup() = {}", Util.getGlobalThreadGroup());
            
            ThreadGroup rootGroup = getRootThreadGroup();
            ThreadGroup groups[] = new ThreadGroup[rootGroup.activeGroupCount()]; 
            rootGroup.enumerate(groups);
            
            for (int i = 0; i < groups.length; i++) {
                if (groups[i].getName().equals("JGroups")) {
                    final ThreadGroup jgroupsGroup = groups[i];
                    jgroupsGroup.interrupt();
                    
                    Thread thread = new Thread() {
                      @Override
                      public void run() {
                        while (jgroupsGroup.activeCount() > 0) {                        
                        }
                        jgroupsGroup.destroy();
                      }  
                    };
                    thread.start();
                }
            }
        } catch (SecurityException e) {
            logger.error(e.getMessage(), e);
        } catch (NoSuchFieldException e) {
            logger.error(e.getMessage(), e);
        } catch (IllegalArgumentException e) {
            logger.error(e.getMessage(), e);
        } catch (IllegalAccessException e) {
            logger.error(e.getMessage(), e);
        }        
    }
    
    private ThreadGroup getRootThreadGroup() {
        ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
        
        while (currentGroup.getParent() != null) {
            currentGroup = currentGroup.getParent();
        }
        
        return currentGroup;
    }
    
}


Но все равно выдает, что есть утечка памяти. Теперь уже в таких местах
Код

The leaking classloader is being held
in org.springframework.security.access.method.AbstractMethodSecurityMetadataSource

The leaking classloader is being held
in org.springframework.security.web.access.expression.WebSecurityExpressionRoot


И раз было еще что-то с таймерами связано. Кроме того в логах появляется запись. Не знаю, связана ли она с изменениями, или была и раньше.
Код

01.11.2012 11:49:19 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: A web application appears to have started a thread named [Resource Destroyer in BasicResourcePool.close()] but has failed to stop it. This is very likely to create a memory leak.


Я в механизме работы потоков в джаве разбираюсь плохо. Поэтому два вопроса:
1) Правильно ли я пофиксил проблемму?
2) Если нет, то как правильно?

PM MAIL WWW   Вверх
jk1
Дата 1.11.2012, 16:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник
Сообщений: 1168
Регистрация: 17.10.2008
Где: Санкт-Петербург

Репутация: 5
Всего: 75



А профайлер подтверждает, что память течет?
Просто у plumbr бывают ложные срабатывания, чего и сами создатели не отрицают


--------------------
Opinions are like assholes — everybody has one
PM MAIL   Вверх
lamao
Дата 2.11.2012, 11:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



Нашел соответсвующий баг в джире jgroups globalThreadGroup not destroyed creates a classloader memory leak . Хех, надо было сразу туда лезть. Правда фикс, который они предлагают не работает и по-моему работать и не должен. В моем варианте (я его еще несколько модифицировал) остается 30 потоков, которые видно просто игнорируют метод interrupt().

Вот мой последний код. Он зацикливается в while (globalThreadGroup.activeCount() > 0) {
Код

public void contextDestroyed(ServletContextEvent sce) {
        try {
            ThreadGroup globalThreadGroup = Util.getGlobalThreadGroup();
            Field field = Util.class.getDeclaredField("GLOBAL_GROUP");
            field.setAccessible(true);
            field.set(null, null);
            logger.info("Util.getGlobalThreadGroup() = {}", Util.getGlobalThreadGroup());
            
            if (!globalThreadGroup.isDestroyed()) {

                if (globalThreadGroup.activeCount() > 0) {
                    logger.warn("Active threads still running in JGroups global thread group.  Waiting 2 seconds.");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException exception) {
                        Thread.currentThread().interrupt();
                    }
                
                }

                while (globalThreadGroup.activeCount() > 0) {
                    logger.warn("{} active threads still running in JGroups global " +
                         "thread group.  Trying to interrupt and waiting for 500 ms",
                         globalThreadGroup.activeCount());
                    globalThreadGroup.interrupt();
                    Thread.sleep(500);
                }
                globalThreadGroup.destroy();
                logger.info("JGroups global thread group is destroyed.");
            }
        } catch (Exception exception) {
            logger.error("Error while destroying JGroups global thread group.", exception);
        }        
    }


Используется это либа как зависимость в артефакте ehcache-replication (у нас). Так глядя на помник в мавен репозитории, вижу, что в последней версии (1.7) они поменяли версию зависимости на 3.1, где как общеают разработчики этот баг пофикшен.

Добавлено через 7 минут и 2 секунды
И да, профайлер подтверждает утечку. Использую дефолтный Java VisualVM. Количество потоков при рестарте не увеличивается, heap, кол-во классов и PermGen увеличивается существенно.

Это сообщение отредактировал(а) lamao - 2.11.2012, 11:28
PM MAIL WWW   Вверх
lamao
Дата 6.11.2012, 17:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



В общем ребята из комьюнити обещали пофиксить в версии 3.3 (так в fixVersion). Посмотрим как скоро это будет.

А корневая причина бага в том, что то ли группа потоков, то ли поток содержит ссылку на класслоадер. Плюс группа потоков прикрепляется как дочерняя к системной группе. И вот получается если ее не убить (а это нельзя сделать не закончив те 30 потоков, которые не убиваются) то будет висячий обьект (с точки зрения нашего приложения) который содержит ссылку на класслоадер приложения, который содержит ссылку на все классы приложения...  smile Это около 6000 при каждом редеплое  smile 
PM MAIL WWW   Вверх
COVD
Дата 8.11.2012, 08:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1655
Регистрация: 26.7.2005

Репутация: 4
Всего: 43



Цитата

В приложении одним из артефактов используется jgroups 

А нельзя сделать один общий jgroups, который стартует с Томкатом и выключается только с ним же? И забыть.
PM MAIL   Вверх
lamao
Дата 9.11.2012, 13:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



Цитата(COVD @ 8.11.2012,  08:17)
Цитата

В приложении одним из артефактов используется jgroups 

А нельзя сделать один общий jgroups, который стартует с Томкатом и выключается только с ним же? И забыть.

Нет нельзя. На одном сервере запускается два приложения, которые общаются между собой (через сокеты в конце концов вроде). Нужно поднимать для каждого свой. 
Короче, записал я это все в их джиру, обещают в 3.3 все же пофиксить.

Добавлено через 25 секунд
--------------------
PM MAIL WWW   Вверх
COVD
Дата 9.11.2012, 14:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1655
Регистрация: 26.7.2005

Репутация: 4
Всего: 43



Почему нельзя. Есть же контекст сервера, который доступен приложениям, если я не путаю. Я примерно так использую Hazelcast, который тоже использует мультикастинг, а возможно и использует jgroups для реализации этого. И jar Hazelcast'a в единственном экземпляре лежит у меня в папке lib Томката, а все веб-приложения имеют доступ к общей памяти, которая еще и распределенная, потому что доступна другому Томкату и его приложениям тоже.

p.s.

Когда приложения стартуют-останавливаются, они должно входить и выходить из группы. Но обьект группы может быть статический, один на сервер. Он инициализируется один раз при старте сервера. 

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

Это сообщение отредактировал(а) COVD - 9.11.2012, 16:01
PM MAIL   Вверх
lamao
Дата 9.11.2012, 17:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



Хм... Не знаю. Возможно и можно будет использовать общую библиотеку на уровне сервера. Мы jgroups тоже используем не напрямую. Его использует ehcache для репликации при распределенном кэше. Вот надо или с jgroups что-то решить, либо использовать другой транспорт. 
PM MAIL WWW   Вверх
COVD
Дата 9.11.2012, 19:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1655
Регистрация: 26.7.2005

Репутация: 4
Всего: 43



Тогда, возможно, ehcache имеет смысл вынести из приложений на уровень сервера. Веб приложения должны хорошо делать свою основную работу - обрабатывать http. От остального их лучше избавлять.

Это сообщение отредактировал(а) COVD - 9.11.2012, 19:16
PM MAIL   Вверх
lamao
Дата 13.11.2012, 13:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 73
Регистрация: 23.4.2008

Репутация: нет
Всего: нет



В принципе это возможно, но тогда потребуется конфигурирования томката при установке приложения. Что для нас есть некоторый минус.
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java EE (J2EE) и Spring | Следующая тема »


 




[ Время генерации скрипта: 0.0520 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.