JMX: как предотвратить утечку памяти Classloader в контейнере сервлетов?

Мне интересно, следует ли и как мне иметь дело с MBeans, которые зарегистрированы прямо или косвенно из моего приложения, которое развертывается в контейнере сервлетов.

В большинстве случаев есть два варианта получения MBeanServer, которые вы можете использовать для регистрации.

  • создайте свой собственный MBeanServer с помощью MBeanServerFactory.createMBeanServer()

  • Используйте ManagementFactory.getPlatformMBeanServer()

При использовании первого варианта легко отменить регистрацию всех компонентов MBean: просто вызовите MBeanServer.releaseMBeanServer(myMBeanServer).

А как насчет второго варианта, который часто используется во многих сторонних приложениях? (и, кстати, это также рекомендуемый путь от Sun/Oracle).

Поскольку используется платформа MBeanServer, ее регистрация не будет отменена при уничтожении контекста сервлета, но, что еще хуже, она по-прежнему содержит ссылку на загрузчик классов веб-приложения.
Как следствие, все статические ссылки веб-приложения не будет выпущен, что приведет к утечке.

Если вы хотите проверить это: просто разверните простое веб-приложение, которое выделяет массив 100 МБ, на который ссылаются статически и который использует драйвер oracle jdbc (он зарегистрирует диагностический MBean с помощью сервера mbean платформы), развернутый на tomcat. Остановите приложение и перезапустите его — повторите это, и вы нажмете OutOfMemoryError.

Вопросы:

  • Должен ли я иметь дело с этими проблемами в целом или это проблема контейнера сервлетов и/или сторонней библиотеки?

  • Есть ли способ получить все MBeans MBeanServer, классы которых загружаются определенным ClassLoader?

  • Что я могу сделать, чтобы предотвратить это? Должен ли я отслеживать все зарегистрированные компоненты MBean на платформе MBeanServer и отменять их регистрацию в течение contextDestroyed()?


person MRalwasser    schedule 20.06.2011    source источник
comment
Возможно, взгляните на stackoverflow.com/questions/386892/   -  person nfechner    schedule 20.06.2011


Ответы (4)


Что я могу сделать, чтобы предотвратить это? Должен ли я отслеживать все зарегистрированные MBeans на платформе MBeanServer и отменять регистрацию во время contextDestroyed()?

Это был мой стандартный совет. Я не знаю лучшего варианта.

person Brett Kail    schedule 20.06.2011

Я использую такой злой сторонний. Чтобы обеспечить правильное отключение контекста сервлета, я перечисляю bean-компоненты, используя mbeanServer.queryMBeans(null, null), а затем unregisterMBean() bean-компоненты, которые находятся в домене третьей стороны.

Set<ObjectInstance> beans = mbeanServer.queryMBeans(null, null);
for (ObjectInstance objectInstance : beans) {
    if (objectInstance.getObjectName().getDomain().equals("third-party-domain")) {
        try {
            mbeanServer.unregisterMBean(objectInstance.getObjectName());
        } catch (MBeanRegistrationException exception) {
            //error handling
        } catch (InstanceNotFoundException exception) {
            //error handling
        }
    }
}
person gawi    schedule 16.05.2013
comment
Я бы также отменил регистрацию только тех MBean, которые были загружены загрузчиком классов веб-приложения: if (mbeanServer.getClassLoaderFor(objectInstance.getObjectName() == Thread.currentThread().getContextClassLoader()) - person John29; 17.04.2015

Что говорит бкаил. Кроме того, если вы используете такую ​​​​инфраструктуру, как Spring (см. MBeanExporter), она должна позаботиться об отмене регистрации ваших объектов JMX при завершении работы Context, что должно произойти как часть повторного развертывания веб-приложения.

person maximdim    schedule 21.06.2011

в то время как вы можете получить все Mbeans и проверить, был ли он загружен загрузчиком классов контейнера, а затем отменить его регистрацию. Пример кода:

final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
final Set<ObjectName> allMBeanNames = mBeanServer.queryNames(new ObjectName("*:*"), null);
for(ObjectName objectName : allMBeanNames) {
  final ClassLoader mBeanClassLoader = mBeanServer.getClassLoaderFor(objectName);
  if(containnerClasssloader.isClassLoaderOrChild(mBeanClassLoader)) { // MBean loaded by containnerClasssloader  
        mBeanServer.unregisterMBean(objectName);
 }
}

Я рекомендую вам прочитать проект classloader-leak-prevention с github. в проекте есть ряд методов для борьбы с утечкой загрузчика классов из контейнеров. для этого вопроса вы можете увидеть MBeanCleanUp.java

person Geker    schedule 05.11.2018
comment
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. – Из обзора - person atline; 05.11.2018
comment
потому что я не автор. Я думаю, что публиковать контент - не очень хорошая идея. - person Geker; 05.11.2018
comment
Что ж, рецензенты могут выбрать вариант, Link-only answers will be deleted by stackoverflow позже, поэтому предлагаю вам добавить некоторые важные вещи в свой пост, не нужен весь код. Так же, как сайт сказал Link-only answers can become invalid if the linked page changes. Я думаю, что сайт просто хочет сделать ответ всегда полезным для всех читателей, не зависящим от другого сайта. - person atline; 05.11.2018
comment
хорошо, я изменяю ответ, добавляю основной код, чтобы объяснить, как решить вопрос. - person Geker; 05.11.2018