Разгрузка классов в Java?

174

У меня есть пользовательский загрузчик классов, чтобы приложение для настольных компьютеров могло динамически начинать загрузку классов с сервера приложений, с которым мне нужно поговорить. Мы сделали это, так как количество банок, необходимых для этого, просто смешно (если мы хотим отправить их). У нас также возникают проблемы с версиями, если мы не загружаем классы динамически во время выполнения из библиотеки AppServer.

Теперь я столкнулся с проблемой, когда мне нужно поговорить с двумя разными AppServer и обнаружил, что в зависимости от того, чьи классы я загружаю первым, я могу плохо сломаться ... Есть ли способ принудительной выгрузки класса без фактического уничтожения JVM?

Надеюсь, это имеет смысл

el_eduardo
источник
У вас есть загрузчик классов для каждой банки? Как OSGI контейнеры архивов для выгрузки загрузчика классов? Похоже, в классе classloader нет API выгрузки?
hetaoblog

Ответы:

190

Единственный способ, которым Класс может быть выгружен, - это если используемый Classloader является сборщиком мусора. Это означает, что ссылки на каждый отдельный класс и на самого загрузчика классов должны идти по пути додо.

Одним из возможных решений вашей проблемы является наличие Classloader для каждого файла JAR и Classloader для каждого из AppServers, который делегирует фактическую загрузку классов конкретным загрузчикам классов Jar. Таким образом, вы можете указать разные версии файла JAR для каждого сервера приложений.

Это не тривиально, хотя. Платформа OSGi стремится сделать именно это, так как каждый пакет имеет свой загрузчик классов и зависимости определяются платформой. Возможно, хорошим решением было бы взглянуть на это.

Если вы не хотите использовать OSGI, одной из возможных реализаций может быть использование одного экземпляра класса JarClassloader для каждого файла JAR.

И создайте новый класс MultiClassloader, который расширяет Classloader. Этот класс внутренне будет иметь массив (или List) JarClassloaders, а в методе defineClass () будет перебирать все внутренние загрузчики классов до тех пор, пока не будет найдено определение или не будет сгенерировано исключение NoClassDefFoundException. Для добавления новых классов JarClassloaders в класс может быть предоставлена ​​пара методов доступа. Существует несколько возможных реализаций в сети для MultiClassLoader, так что вам может даже не потребоваться написать свою собственную.

Если вы создаете экземпляр MultiClassloader для каждого соединения с сервером, в принципе, возможно, что каждый сервер использует разные версии одного и того же класса.

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

Марио Ортегон
источник
31
Также обратите внимание, что в соответствии с java.sun.com/docs/books/jls/second_edition/html/… выгрузка классов является оптимизацией и, в зависимости от реализации JVM, может происходить или не происходить.
5
В качестве более простой и легкой альтернативы OSGi, попробуйте JBoss Modules - модульная загрузка классов с загрузчиком классов на модуль (группа jar-файлов).
Ондра Жижка
42

Да, есть способы загружать классы и «выгружать» их позже. Хитрость заключается в том, чтобы реализовать свой собственный загрузчик классов, который находится между загрузчиком классов высокого уровня (загрузчиком классов System) и загрузчиками классов серверов приложений, и надеяться, что загрузчики классов сервера приложений делегируют загрузку классов верхним загрузчикам. ,

Класс определяется его пакетом, его именем и загрузчиком классов, который он первоначально загрузил. Запрограммируйте загрузчик классов «прокси», который загружается первым при запуске JVM. Процедура:

  • Программа запускается и настоящий «главный» класс загружается этим прокси-загрузчиком классов.
  • Каждый класс, который затем обычно загружается (т.е. не через другую реализацию загрузчика классов, которая может нарушить иерархию), будет делегирован этому загрузчику классов.
  • Прокси-загрузчик классов делегирует java.xи sun.xсистемному загрузчику классов (они не должны загружаться через какой-либо другой загрузчик классов, кроме системного загрузчика классов).
  • Для каждого заменяемого класса создайте экземпляр загрузчика классов (который действительно загружает класс и не передает его родительскому загрузчику классов) и загружайте его через него.
  • Сохраните пакет / имя классов как ключи, а загрузчик классов - как значения в структуре данных (т. Е. Hashmap).
  • Каждый раз, когда прокси-загрузчик классов получает запрос на класс, который был загружен ранее, он возвращает класс из загрузчика классов, сохраненного ранее.
  • Этого должно быть достаточно, чтобы найти байтовый массив класса вашим загрузчиком классов (или «удалить» пару ключ / значение из вашей структуры данных) и перезагрузить класс на случай, если вы захотите изменить его.

Сделано прямо там, не должно прийти ClassCastException или LinkageError и т. Д.

Для получения дополнительной информации об иерархиях загрузчиков классов (да, это именно то, что вы реализуете здесь; -) посмотрите на «Серверное программирование на Java» Теда Ньюарда - эта книга помогла мне реализовать нечто очень похожее на то, что вы хотите.

Георгий
источник
3
Я не понимаю людей, которые отметили -1 для этого ответа, не оставляя комментарий. Для меня выглядит хорошо. Возможно, иметь по одному ClassLoaderна класс - это слишком много, один ClassLoaderна JAR имеет смысл. Может быть более конкретно о том, как принудительно загрузить класс в предложенной схеме? Например, как я могу гарантировать, что экземпляры классов, загруженных ClassLoaderA, не ссылаются на экземпляры, загруженные ClassLoaderB?
dma_k
@dma_k точно, ответ хороший, но он не затрагивает ключевые моменты, которые вы упомянули.
мелькает
@ Георгий, существует ли существующая реализация, которую мы можем использовать для этого?
Sled
1
Было бы очень очень полезно, если бы вы могли предоставить пример кода Java. Чтобы быть точным, я ищу Как выгрузить классы, используя CustomClassLoader, но не повезло.
Срихарша грв
@ Sriharshag.rv Вы пробовали это и реализовали пример?
niaomingjian
17

Я написал собственный загрузчик классов, из которого можно выгружать отдельные классы без GCing загрузчика классов. Jar Class Loader

Камран
источник
Работает как шарм :). Есть ли способ выгрузить все файлы классов из JAR-файла?
Эрксен
К сожалению, не в данный момент. Но займусь этим. Может быть в будущих выпусках.
Камран
Кстати, я нашел небольшой обходной путь. Если у вас есть один JarClassLoaderфайл для каждого загруженного вами jar-файла, вы можете вызвать getLoadedClasses()его, затем выполнить итерацию по каждому и выгрузить его.
Эрксен
12

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

Большинство людей, нуждающихся в подобных вещах, в конечном итоге используют OSGi . OSGi действительно мощный и удивительно легкий и простой в использовании,

Стив г
источник
7

Вы можете выгрузить ClassLoader, но вы не можете выгрузить определенные классы. Более конкретно, вы не можете выгружать классы, созданные в ClassLoader, который не находится под вашим контролем.

Если возможно, я предлагаю использовать свой собственный ClassLoader, чтобы вы могли выгрузить.

Джейсон Коэн
источник
4

Классы имеют неявную строгую ссылку на свой экземпляр ClassLoader, и наоборот. Они мусор, как с объектами Java. Без использования интерфейса инструментов или подобного вам нельзя удалить отдельные классы.

Как всегда, вы можете получить утечки памяти. Любая сильная ссылка на один из ваших классов или загрузчик классов приведет к утечке. Это происходит, например, с реализациями Sun ThreadLocal, java.sql.DriverManager и java.beans.

Том Хотин - Tackline
источник
-1

Если вы в прямом эфире наблюдаете, работает ли класс выгрузки в JConsole или как-то еще, попробуйте также добавить java.lang.System.gc()в конце логику выгрузки класса. Это явно вызывает сборщик мусора.

Александр Дрозд
источник
3
Осторожно: System.gc () не обязательно вызывать GC. Он только просит jvm запустить его, но не применяет его. И IME это часто не запускает GC: - \
Juh_