Mamy złożoną aplikację działającą pod Glassfish V2.1.1. Aby móc ładować nasz kod dynamicznie, zaimplementowaliśmy CustomClassloader, który jest w stanie przedefiniować klasy. Zachowanie jest dość łatwe: gdy klasa ładowana dynamicznie zmieniła się, bieżąca instancja programu CustomClassloader zostanie "upuszczona", a nowa zostanie utworzona w celu ponownego zdefiniowania potrzebnych klas.Classloader nie jest zbiorem śmieci, nawet bez korzeni GC
Działa to dobrze, z tą różnicą, że po kilku razach ta sama klasa została przeładowana (stąd za każdym razem, gdy tworzony jest nowy CustomClassloader), dostajemy błąd przestrzeni PermGen, ponieważ inne instancje CustomClassloader nie są zbierane. (Nie powinno być tylko jedna instancja tej klasy)
Próbowałem różnych metod, aby wyśledzić, gdzie wyciek jest:
- visualvm => I dokonać zrzutu sterty i wyodrębnić wszystkie instancje CustomClassloader . Widzę, że żaden z nich nie został sfinalizowany. Kiedy sprawdzam najbliższy katalog główny GC, visualvm mówi mi, że nie ma go (z wyjątkiem ostatniego wystąpienia, ponieważ jest to "prawdziwy" używany).
- jmap/jhat => Daje mi prawie taki sam wynik: widzę wszystkie wystąpienia programu CustomClassloader, a gdy kliknę link, aby zobaczyć, gdzie są odniesienia na jednym z nich, otrzymuję pustą stronę, co oznacza, że nie ma żadnej. ..
- Eclipse Memory Analyzer Tool => mam dziwny wynik po uruchomieniu następujące kwerendy OQL:
SELECT c FROM INSTANCEOF my.package.CustomClassloader c
jest tylko jeden wynik, wskazując, że jest tylko jeden przypadek, który jest oczywiście nie poprawne.
Sprawdziłem również to link i zaimplementowałem wydanie niektórych zasobów, gdy tworzony jest nowy CustomClassloader, ale nic się nie zmienia: pamięć PermGena ciągle rośnie.
Prawdopodobnie brakuje mi czegoś, a różnica między punktami (1-2) i (3) pokazuje coś, czego nie rozumiem. Gdzie mogę sprawdzić, co jest nie tak? Ponieważ wszystkie tutoriale, których używałam, pokazują, jak wyszukiwać wyciekające odniesienia za pomocą funkcji "Wyszukaj najbliższy GC" (aw moim przypadku nie ma żadnej), nie wiem, jak mogę śledzić błąd.
EDYCJA 1: Przesłałem przykład zrzutu sterty here. ClassLoader nie jest rozładowywany można wybrać w visualvm z następującym zapytaniem: select s from saierp.core.framework.system.SAITaskClassLoader s
Można zauważyć, że istnieją 4 wystąpienia i trzy pierwsze powinny zostać zebrane, ponieważ nie ma głównego GC ... Musi być gdzieś odniesienie, ale ja nie wiem, jak mogę to wyszukać. Każda wskazówka jest mile widziany :)
EDYCJA 2: Po głębszych testach widzę bardzo dziwny wzór. Wydaje się, że wyciek zależy od danych ładowanych przez OpenJPA: jeśli nie załadowano żadnych nowych danych, to program ładujący klasy może być GCed, w przeciwnym razie nie. Oto kod używam, kiedy utworzyć nową SAITaskClassLoader do 'jasne' stary:
PCRegistry.deRegister(cl);
LogFactory.release(cl);
ResourceBundle.clearCache(cl);
Introspector.flushCaches();
= wzór 1 (ClassLoader jest GCed): =
- New SAITaskClassLoader
- załadować dane D1, D2, ...Dn
- New SAITaskClassLoader
- Dane obciążenia D1, D2, ..., Dn
- ...
= Wzór 2 (classloader nie jest GCed): =
- New SAITaskClassLoader
- Dane obciążenia D1, D2, D3
- New SAITaskClassLoader
- Dane obciążenia D3, D4, D5
- New SAITaskClassLoader
- ładowania danych D5, D6, D7
- ...
We wszystkich przypadkach , SAITaskClassLoader, które zostały wyczyszczone, nie mają głównego katalogu GC. Używamy OpenJPA 1.2.1.
Dzięki & poważaniem
Próbowałeś wzoru Singleton? – OmniOwl
Tak, faktycznie CustomClassloader jest Singleton. – ctabin
To dziwne. Gdyby tak było, nigdy nie powinieneś mieć wielu obiektów tego typu, które nigdy nie zostaną sfinalizowane. Pierwszy obiekt cię powstrzyma ... hmm. Czy próbowałeś ustawić wyrzucony obiekt na wartość null? – OmniOwl