Zwalnianie klas w Javie?

174

Mam niestandardowy moduł ładujący klasy, dzięki czemu aplikacja komputerowa może dynamicznie rozpoczynać ładowanie klas z serwera aplikacji, z którym muszę porozmawiać. Zrobiliśmy to, ponieważ ilość słoików, które są do tego potrzebne, jest absurdalna (gdybyśmy chcieli je wysłać). Mamy również problemy z wersją, jeśli nie ładujemy klas dynamicznie w czasie wykonywania z biblioteki AppServer.

Teraz po prostu napotkałem problem, w którym muszę porozmawiać z dwoma różnymi serwerami aplikacji i stwierdziłem, że w zależności od tego, czyje klasy załaduję najpierw, mogę źle się zepsuć ... Czy istnieje sposób, aby wymusić wyładowanie klasy bez faktycznego zabijania JVM?

Mam nadzieję, że to ma sens

el_eduardo
źródło
Czy masz classloader dla każdego słoika? Jak archiwizuje się kontenery OSGI w celu wyładowania modułu ładującego klasy? Wygląda na to, że w klasie classloader nie ma interfejsu API zwalniania?
hetaoblog

Odpowiedzi:

190

Jedynym sposobem, w jaki można zwolnić Class, jest to, że używany Classloader jest usuwany z pamięci. Oznacza to, że odniesienia do każdej klasy i samego modułu ładującego klasy muszą iść drogą dodo.

Jednym z możliwych rozwiązań problemu jest posiadanie modułu ładującego klasy dla każdego pliku jar i modułu ładującego klasy dla każdego serwera AppServers, który deleguje rzeczywiste ładowanie klas do określonych programów ładujących klasy Jar. W ten sposób możesz wskazać różne wersje pliku jar dla każdego serwera aplikacji.

Nie jest to jednak trywialne. Platforma OSGi stara się właśnie to zrobić, ponieważ każdy pakiet ma inny program ładujący klasy, a zależności są rozwiązywane przez platformę. Może dobrym rozwiązaniem byłoby przyjrzenie się temu.

Jeśli nie chcesz używać OSGI, jedną z możliwych implementacji może być użycie jednej instancji klasy JarClassloader dla każdego pliku JAR.

I utwórz nową klasę MultiClassloader, która rozszerza Classloader. Ta klasa wewnętrznie miałaby tablicę (lub Listę) JarClassloaders, aw metodzie defineClass () iterowałaby przez wszystkie wewnętrzne programy ładujące do momentu znalezienia definicji lub wyrzucenia wyjątku NoClassDefFoundException. Można udostępnić kilka metod akcesorów, aby dodać nowe elementy JarClassloaders do klasy. Istnieje kilka możliwych implementacji MultiClassLoadera w sieci, więc możesz nawet nie potrzebować pisać własnej.

Jeśli instalujesz MultiClassloader dla każdego połączenia z serwerem, w zasadzie możliwe jest, że każdy serwer używa innej wersji tej samej klasy.

Wykorzystałem pomysł MultiClassloader w projekcie, w którym klasy zawierające skrypty zdefiniowane przez użytkownika musiały być ładowane i wyładowywane z pamięci i działało całkiem dobrze.

Mario Ortegón
źródło
31
Należy również zauważyć, że zgodnie z java.sun.com/docs/books/jls/second_edition/html/ ... rozładowywanie klas jest optymalizacją i, w zależności od implementacji maszyny JVM, może faktycznie wystąpić lub nie.
5
Jako łatwiejszą i lekką alternatywę dla OSGi, wypróbuj JBoss Modules - modułowe ładowanie klas z klasą ładującą na moduł (grupę słoików).
Ondra Žižka
42

Tak, istnieją sposoby na załadowanie klas i późniejsze ich „wyładowanie”. Sztuczka polega na zaimplementowaniu własnego modułu ładującego klasy, który znajduje się między programem ładującym klasy wysokiego poziomu (moduł ładujący klasy System) a programami ładującymi klasy serwera (serwerów) aplikacji i mieć nadzieję, że programy ładujące klasy serwera aplikacji delegują ładowanie klas do modułów ładujących wyższych .

Klasa jest definiowana przez jej pakiet, jej nazwę i program ładujący klasę, który został pierwotnie załadowany. Zaprogramuj program ładujący klasy „proxy”, który jest pierwszym ładowanym podczas uruchamiania maszyny JVM. Przepływ pracy:

  • Program uruchamia się i prawdziwa "główna" klasa jest ładowana przez ten program ładujący klasy proxy.
  • Każda klasa, która jest normalnie ładowana (tj. Nie przez inną implementację modułu ładującego klasy, która mogłaby złamać hierarchię), zostanie delegowana do tego programu ładującego klasy.
  • Delegaci modułu ładującego klasy proxy java.xi sun.xmoduł ładujący klasy systemu ( nie mogą być ładowani przez żaden inny program ładujący klasy niż systemowy moduł ładujący).
  • Dla każdej wymiennej klasy utwórz wystąpienie modułu ładującego klasy (który naprawdę ładuje klasę i nie deleguje jej do modułu ładującego klasy nadrzędnej) i załaduj go przez to.
  • Przechowuj pakiet / nazwę klas jako klucze, a program ładujący klasy jako wartości w strukturze danych (np. Hashmap).
  • Za każdym razem, gdy moduł ładujący klasy proxy otrzymuje żądanie dotyczące klasy, która została wcześniej załadowana, zwraca klasę z wcześniej zapisanego modułu ładującego klasy.
  • Powinno wystarczyć zlokalizowanie tablicy bajtów klasy przez program ładujący klasy (lub „usunięcie” pary klucz / wartość ze struktury danych) i ponowne załadowanie klasy na wypadek, gdybyś chciał ją zmienić.

Sporządzono w tym miejscu, nie powinien pojawić się wyjątek ClassCastException lub LinkageError itp.

Aby uzyskać więcej informacji na temat hierarchii ładujących klas (tak, właśnie to tutaj implementujesz; -) spójrz na „Server-Based Java Programming” Teda Newarda - ta książka pomogła mi zaimplementować coś bardzo podobnego do tego, czego chcesz.

Georgi
źródło
3
Nie rozumiem ludzi, którzy zaznaczyli -1 dla tej odpowiedzi bez zostawiania komentarza. Jak dla mnie wygląda dobrze. Być może posiadanie jednego ClassLoaderna klasę to trochę za dużo, jeden ClassLoaderna JAR ma sens. Czy może być bardziej szczegółowe informacje o tym, jak wymusić przesyłanie zajęć w proponowanym schemacie? Np. Jak mogę zagwarantować, że instancje klas ładowane przez ClassLoaderA nie są odwoływane przez instancje ładowane przez ClassLoaderB?
dma_k,
@dma_k dokładnie, odpowiedź jest dobra, ale nie dotyczy kluczowych punktów, o których wspomniałeś.
zinking
@Georgi Czy istnieje implementacja, którą możemy wdrożyć / ponownie wykorzystać?
Sled
1
Byłoby bardzo pomocne, gdybyś mógł podać przykładowy kod java. Aby być precyzyjnym, szukam Jak wyładować klasy za pomocą CustomClassLoader, ale nie miałem szczęścia.
Sriharsha grv
@ Sriharshag.rv Czy próbowałeś tego i zaimplementowałeś próbkę?
niaomingjian
17

Napisałem własny classloader, z którego można wyładować poszczególne klasy bez GCing the classloader. Jar Class Loader

Kamran
źródło
Działa jak marzenie :). Czy istnieje metoda wyładowania wszystkich plików klas z pliku jar?
Ercksen
Niestety nie w tej chwili. Ale przyjrzę się temu. Może znajdować się w przyszłych wersjach.
Kamran
Nawiasem mówiąc, znalazłem małe obejście. Jeśli masz jeden JarClassLoaderdla każdego załadowanego pliku jar, możesz go wywołać getLoadedClasses(), a następnie iterować po każdym i wyładować.
Ercksen
12

Classloadery mogą być trudnym problemem. Możesz napotkać problemy szczególnie, jeśli używasz wielu programów ładujących klasy i nie masz jasno i rygorystycznie zdefiniowanych ich interakcji. Myślę, że aby rzeczywiście móc wyładować klasę, którą zamierzasz, musisz usunąć wszystkie odwołania do klas (i ich instancji), które próbujesz usunąć.

Większość ludzi, którzy muszą robić tego typu rzeczy, korzysta z OSGi . OSGi jest naprawdę potężny i zaskakująco lekki i łatwy w użyciu,

Steve g
źródło
7

Możesz zwolnić ClassLoader, ale nie możesz zwolnić określonych klas. Mówiąc dokładniej, nie można zwolnić klas utworzonych w ClassLoader, nad którymi nie masz kontroli.

Jeśli to możliwe, sugeruję użycie własnego ClassLoadera, abyś mógł rozładować.

Jason Cohen
źródło
4

Klasy mają niejawne silne odwołanie do ich wystąpienia ClassLoader i na odwrót. Są zbierane jako śmieci, tak jak w przypadku obiektów Java. Bez wciskania interfejsu narzędzi lub podobnego, nie możesz usunąć poszczególnych klas.

Jak zawsze możesz mieć wycieki pamięci. Każde silne odniesienie do jednej z twoich klas lub programu ładującego klasy spowoduje wyciek całej sprawy. Dzieje się tak na przykład w przypadku implementacji ThreadLocal, java.sql.DriverManager i java.beans firmy Sun.

Tom Hawtin - haczyk
źródło
-1

Jeśli oglądasz na żywo, czy wyładowywanie zajęć działało w JConsole, czy coś w tym stylu, spróbuj również dodać java.lang.System.gc()na końcu logiki wyładowywania klasy. To jawnie uruchamia Garbage Collector.

Aleksander Drozd
źródło
3
Uwaga: System.gc () nie musi wywoływać GC. Prosi tylko jvm o jego uruchomienie, ale nie wymusza tego. I IME często nie uruchamia GC: - \
Juh_