6

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:

  1. 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).
  2. 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. ..
  3. 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): =

  1. New SAITaskClassLoader
  2. załadować dane D1, D2, ...Dn
  3. New SAITaskClassLoader
  4. Dane obciążenia D1, D2, ..., Dn
  5. ...

cl-gc

= Wzór 2 (classloader nie jest GCed): =

  1. New SAITaskClassLoader
  2. Dane obciążenia D1, D2, D3
  3. New SAITaskClassLoader
  4. Dane obciążenia D3, D4, D5
  5. New SAITaskClassLoader
  6. ładowania danych D5, D6, D7
  7. ...

cl-nogc

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

+0

Próbowałeś wzoru Singleton? – OmniOwl

+0

Tak, faktycznie CustomClassloader jest Singleton. – ctabin

+0

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

Odpowiedz

6

Bez fragmentów kodu źródłowego CustomClassLoader lub rzeczywistych wysypisk kupa, to będzie bardzo trudno wyśledzić problem. Twoja CustomClassLoader nie może być pojedyncza. Jeśli tak, twój projekt nie może działać (lub coś przeoczyłem).

Należy uzyskać listę instancji ClassLoader instancji typu CustomClassLoader i wyśledzić odniesienia do tych obiektów.

Stanowiska te mogą pomóc Ci w jaki sposób analizować go dalej i dostać się do ciemnych detaisls o polowanie na przecieki classloader:

+0

Dzięki za twoje linki. Kiedy mówię, że CustomClassloader jest singleton, to prawda z klasy zewnętrznej, jedyną metodą uzyskania odniesienia do niej jest static getInstance(). Będzie mógł zobaczyć, kiedy nastąpi zmiana klasy, i wewnętrznie utworzyć nową instancję, która będzie przechowywana w statycznej zmiennej wewnętrznej. Ten ostatni jest jedynym wyraźnym odnośnikiem w całym zgłoszeniu. – ctabin

+0

Może coś znalazłem. Używam OpenJPA 1.2.1 i jest ten błąd: https://issues.apache.org/jira/browse/GERONIMO-3326 – ctabin

+0

Przesłałem ZIP mojej sterty, żebyś mógł rzucić okiem. – ctabin

2

Wywóz śmieci classloaders jest wyjątkowo trudnym biznesem. Korzystanie JProfiler widzę następujący łańcuch odnośników przychodzących do aktualnie aktywnej ładowarki zwyczaj klasy:

enter image description here

To pokazuje, że masz pole statyczne „singleInstance” w niestandardowej classloader, który odwołuje się do samego programu ładującego klasy. Powinieneś spróbować wyczyścić to pole przy przeniesieniu, aby ułatwić maszynie VM zbieranie programu ładującego klasy.

Uwaga na temat wyniku uzyskanego za pomocą Eclipse MAT: Usuwa wszystkie obiekty, które nie są osiągalne. JProfiler również robi to domyślnie. Tak więc trzy poprzednie programy ładujące klasy powinny być zebrane jako śmieci, ale nie są, ze względu na specjalne reguły, które JVM ma dla klasy GC, które nie są przechwytywane przez standardowe referencje w stercie.

Zastrzeżenie: Moja firma rozwija JProfiler

+0

Dziękuję za odpowiedź, ale jest to poprawne :) Istnieje jedna instancja SAITaskClassLoader, która jest trzymana w zmiennej statycznej. Gdy klasa jest aktualizowana, ta instancja jest usuwana, a nowa jest przechowywana w tej [statycznej] zmiennej. Problem polega na tym, że upuszczone instancje nie są zbiorem śmieci i wszystkie analizatory sterty, które przetestowałem, pokazują, że nie mają one żadnego rootu GC ... W zrzucie 3 z 4 instancji powinny zostać zebrane, ale nigdy nie są, powodując a PermGen OutOfMemoryError po utworzeniu nowych instancji SAITaskClassLoader. – ctabin

+0

W zależności od tego, jak to zrobisz, może to nadal stanowić problem. Niekoniecznie chodzi tu o ścieżki do korzeni GC, GC klasy ładujący ma inne zasady oprócz silnego zasięgu i jest bardzo delikatny. Być może mógłbyś wstawić kolejną warstwę, która nie jest ładowana sama w sobie i trzymać w niej pojedynczej instancji programu ładującego klasy, wtedy masz bardziej wyraźną kontrolę. –

+0

Zidentyfikowałem wzorzec i dodałem kilka wykresów, więc może ktoś może mieć pojęcie, gdzie szukać ... – ctabin

Powiązane problemy