Długowieczna Jawa słaba

Obecnie próbuję zdiagnozować powolny wyciek pamięci w mojej aplikacji. Fakty, które mam do tej pory są następujące.

  • mam zrzut sterty z 4-dniowego uruchomienia aplikacji.
  • ten zrzut sterty zawiera ~800 obiektów WeakReference, które wskazują na obiekty (wszystkie tego samego typu, które będę nazywał Foo do celów tego pytania), zachowując 40mb pamięci.
  • Eclipse Memory Analysis Tool pokazuje, że każdy z obiektów Foo, o których mowa przez te słabe o których nie wspominają żadne inne obiekty. Oczekuję, że dzięki temu Obiekty Foo będą słabo osiągalne i dlatego powinny zostać zebrane w następnym GC.
  • każdy z tych obiektów Foo ma znacznik czasu, który pokazuje, że zostały przydzielone w trakcie 4-dniowego biegu. W tym czasie mam również logi, które potwierdzają, że miało miejsce zbieranie śmieci.
  • ogromna liczba obiektów Foo jest tworzona przez moją aplikację i tylko bardzo mała ich część kończą w tym stanie na wysypisku śmieci. To sugeruje mi, że główną przyczyną jest jakiś stan rasy.
  • Moja aplikacja używa JNI do połączenia z natywną biblioteką. Kod JNI wywołuje NewGlobalRef 4 razy podczas inicjalizacji początku dnia, aby uzyskać odniesienia do klas Java, których używa.

Co może spowodować, że te klasy Foo nie będą zbierane, mimo że będą odwoływane tylko przez słabe (według Eclipse Memory Analyser Narzędzie)?

EDIT1:

@ Mindas WeakReference, którego używam jest odpowiednikiem poniższego przykładowego kodu.
public class FooWeakRef extends WeakReference<Foo>
{
  public long longA;
  public long longB;
  public String stringA;

  public FooWeakRef(Foo xiObject, ReferenceQueue<Foo> xiQueue)
  {
    super(xiObject, xiQueue);
  }
}

Foo nie ma finalizera i żaden finalizer nie będzie brany pod uwagę, dopóki nie zostaną wyczyszczone słabe Refy. Obiekt nie jest finalizowalny, gdy jest słabo osiągalny. zobacz tę stronę po szczegóły .

@ kasten słabość jest czyszczona, zanim obiekt jest finalizowalny. Mój zrzut sterty pokazuje, że to nie stało się.

@jarnbjo odnoszę się do słabego Javadoc:

"Załóżmy, że garbage collector określa w pewnym momencie czasowym, że obiekt jest słabo osiągalny. W tym czasie będzie atomicznie wyczyścić wszystkie słabe odniesienia do tego obiektu i wszystkie słabe odniesienia do innych słabiej osiągalnych obiektów, z których ten obiekt jest osiągalny poprzez łańcuch silnych i miękkich odniesień."

To sugeruje mi, że GC powinno wykrywać fakt, że mój Foo obiekty są "słabo osiągalne "i" w tym czasie " usuwają słabe odniesienia.

Edycja 2

@j - wiem, że 40mb nie brzmi jak dużo, ale obawiam się, że 40mb w 4 dni oznacza 4000mb w 100 dni. Wszystkie dokumenty, które przeczytałem sugerują, że obiekty, które są słabo osiągalne, nie powinny wisieć przez kilka dni. Dlatego jestem zainteresowany wszelkimi innymi wyjaśnieniami na temat tego, w jaki sposób obiekt może być silnie odwołany bez odniesienia pojawiającego się w stercie wysyp.

Spróbuję przydzielić kilka dużych obiektów, gdy niektóre z tych zwisających obiektów Foo są obecne i zobaczę, czy JVM je zbiera. Jednak konfiguracja i ukończenie tego testu zajmie kilka dni.

Edycja 3

@jarnbjo-rozumiem, że nie mam gwarancji, kiedy JDK zauważy, że obiekt jest słabo osiągalny. Jednak spodziewałbym się, że wniosek pod dużym obciążeniem przez 4 dni zapewni wystarczająco dużo możliwości, aby GC zauważył że moje przedmioty są słabo osiągalne. Po 4 dniach jestem mocno podejrzliwy, że pozostałe słabo nawiązujące obiekty zostały jakoś wycieknięte.

Edycja 4

@j flemm-to naprawdę ciekawe! Tak dla jasności, czy mówisz, że GC dzieje się w Twojej aplikacji i nie czyści miękkich / słabych refów? Czy możesz mi podać więcej szczegółów na temat tego, czego używasz konfiguracji JVM + GC? Moja aplikacja używa paska pamięci na 80% sterty, aby uruchomić GC. Zakładałem, że każdy GC starego gen czyści słabe refy. Sugerujesz, że GC zbiera słabe refy tylko wtedy, gdy zużycie pamięci przekroczy wyższy próg? Czy ten wyższy limit można konfigurować?

Edycja 5

@j - twój komentarz o wyczyszczeniu WeakRefs przed SoftRefs jest zgodny z Javadoc który stwierdza: SoftRef: "Załóżmy, że garbage collector określa w pewnym momencie w czasie, że obiekt jest miękko osiągalny. W tym czasie to może wybrać wyczyścić atomicznie wszystkie miękkie odniesienia do tego obiektu i wszystkie miękkie odniesienia do wszelkich innych miękko osiągalnych obiektów, z których ten obiekt jest osiągalny poprzez łańcuch silnych odniesień. W tym samym czasie lub w późniejszym czasie zapyta o nowo wyczyszczone miękkie odniesienia, które są zarejestrowane w kolejkach referencyjnych."

WeakRef: "Załóżmy, że garbage collector określa w pewnym momencie czasowym, że obiekt jest słabo osiągalny. W tym czasie będzie atomicznie wyczyścić wszystkie słabe odniesienia do tego obiektu i wszystkich słabych odniesień do innych słabych obiektów, z których ten obiekt jest osiągalny poprzez łańcuch silnych i miękkich odniesień. Jednocześnie zadeklaruje wszystkie wcześniej słabo dostępne obiekty jako finalizowalne. W tym samym czasie lub w późniejszym czasie zapyta o nowo wyczyszczone słabe referencje, które są zarejestrowane w kolejkach referencyjnych."

Dla jasności, mówisz, że Garbage Collector działa, gdy aplikacja ma więcej niż 50% Darmowa pamięć i w tym przypadku nie usuwa słabych plików? Dlaczego GC w ogóle działa, gdy aplikacja ma >50% wolnej pamięci? Myślę, że Twoja aplikacja jest prawdopodobnie po prostu generowanie bardzo małej ilości śmieci i gdy kolektor działa to wyczyszczenie WeakRefs, ale nie SoftRefs.

EDIT 6

@j flemm-innym możliwym wyjaśnieniem zachowania Twojej aplikacji jest to, że młody gen jest zbierany, ale twoje słabe i miękkie refy są w starym Genie i są usuwane tylko wtedy, gdy stary gen jest zbierane. Dla mojej aplikacji mam statystyki pokazujące, że stary gen jest zbierany, co powinno oznaczać, że WeakRefs się wyczyścić.

Edycja 7

Zaczynam nagrodę za to pytanie. Szukam wiarygodnych wyjaśnień, jak słabe refs mogą nie zostać usunięte podczas GC. Jeśli odpowiedź jest taka, że jest to niemożliwe, chciałbym być skierowany na odpowiednie bity OpenJDK, które pokazują, że WeakRefs jest czyszczony, gdy tylko obiekt zostanie określony jako słaba osiągalność i ta słaba osiągalność jest rozwiązywana za każdym razem, gdy GC działa.
Author: mchr, 2010-10-06

7 answers

W końcu zacząłem sprawdzać kod źródłowy Hotspot JVM i znalazłem następujący kod.

W referenceProcessor.cpp:

void ReferenceProcessor::process_discovered_references(
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor) {
  NOT_PRODUCT(verify_ok_to_handle_reflists());

  assert(!enqueuing_is_done(), "If here enqueuing should not be complete");
  // Stop treating discovered references specially.
  disable_discovery();

  bool trace_time = PrintGCDetails && PrintReferenceGC;
  // Soft references
  {
    TraceTime tt("SoftReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }

  update_soft_ref_master_clock();

  // Weak references
  {
    TraceTime tt("WeakReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }

Funkcja process_discovered_reflist ma następujący podpis:

void
ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)

To pokazuje, że WeakRefs są bezwarunkowo czyszczone przez ReferenceProcessor:: process_discovered_references.

Przeszukanie kodu hotspota dla process_discovered_reference pokazuje, że kolektor CMS (którego używam) wywołuje to metoda z poniższego stosu wywołań.

CMSCollector::refProcessingWork
CMSCollector::checkpointRootsFinalWork
CMSCollector::checkpointRootsFinal

Ten stos wywołań wygląda tak, jakby był wywoływany za każdym razem, gdy uruchomiona jest kolekcja CMS.

Zakładając, że to prawda, jedynym wyjaśnieniem dla długotrwałego, słabo odwołującego się obiektu byłby albo subtelny błąd JVM, albo gdyby GC nie zostało uruchomione.

 3
Author: mchr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-04 15:59:28

Możesz sprawdzić, czy wyciekł problem z classloaderem. Więcej na ten temat można znaleźć w this blog post

 1
Author: Oleg Iavorskyi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-12 20:43:13

Musisz wyjaśnić, jaki jest związek między Foo i WeakReference. Sprawa

class Wrapper<T> extends WeakReference<T> {
  private final T referent;
  public Wrapper(T referent) {
    super(t);
    this.referent = referent;
  }
}

Bardzo różni się od just

class Wrapper<T> extends WeakReferece<T> {
  public Wrapper(T referent) {
    super(t);
  }
}

Lub jego inlined version, WeakReference<Foo> wr = new WeakReference<Foo>(foo).

Więc zakładam, że Twoja sprawa nie jest taka, jak opisałem w moim pierwszym fragmencie kodu.

Jak już powiedziałeś, że pracujesz z JNI, możesz sprawdzić, czy masz jakieś niebezpieczne finalizery. Każdy finalizer powinien mieć finally blokujące wywołanie super.finalize() i jest łatwy do poślizgu.

Prawdopodobnie musisz nam powiedzieć więcej o naturze swoich obiektów, aby zaoferować lepsze pomysły.

 0
Author: mindas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-06 12:15:37

@ iirekm No: WeakReferences są "słabsze" niż SoftReferences, co oznacza, że WeakReference zawsze będzie śmieciem zbieranym przed Softreferencją.

Więcej informacji w tym poście: zrozumienie klas referencyjnych Javy: SoftReference, WeakReference i PhantomReference

Edit: (po przeczytaniu komentarzy) tak na pewno słabe referencje są "słabsze" niż Softreferencje, literówka. : S

Oto kilka przypadków użycia, aby rzucić więcej światła na temat:

  • SoftReference : in-memory cache (obiekt pozostaje żywy, dopóki VM nie uzna, że nie ma wystarczającej ilości mem sterty)
  • WeakReference: automatyczne czyszczenie słuchaczy (obiekt powinien być wyczyszczony w następnym cyklu GC po uznaniu za słabo osiągalny)
  • PhantomReference : unikanie błędów out-of-memory podczas obsługi niezwykle dużych obiektów (gdy zaplanowano w kolejce referencji, wiemy, że obiekt hosta ma być wyczyszczony, bezpiecznie przydzielić kolejny duży obiekt). Pomyśl o tym jako o alternatywie finalize (), bez możliwości przywracania martwych obiektów do życia (tak jak potencjalnie możesz to zrobić za pomocą finalize)

To powiedziawszy, nic nie stoi na przeszkodzie, aby VM (Proszę mnie poprawić, jeśli się mylę) pozwolił słabo osiągalnym obiektom pozostać przy życiu, o ile nie zabraknie mu pamięci (jak w oryginale. przypadku autora).

To jest najlepszy zasób jaki mogłem znaleźć na ten temat: http://www.pawlan.com/monica/articles/refobjs/

Edit 2: Dodano "być" przed cleared in PhantomRef

 0
Author: bjornl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:12

Nie znam Javy, ale możesz używać generative garbage collector , który będzie trzymał twoje obiekty Foo i FooWeakRef same (nie zebrane) tak długo, jak

  • przeszli w starszym pokoleniu
  • jest wystarczająco dużo pamięci, aby przydzielać nowe obiekty w młodszych pokoleniach

Czy dziennik, który wskazuje, że miało miejsce usuwanie śmieci, rozróżnia Kolekcje główne i pomniejsze?

 0
Author: plodoc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-14 22:12:48

Dla niewierzących, którzy twierdzą, że słabe odniesienia są usuwane przed miękkimi odniesieniami:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;


public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
    ReferenceQueue<Object> q = new ReferenceQueue<Object>();
    Map<Reference<?>, String> referenceToId = new HashMap<Reference<?>, String>();
    for(int i=0; i<100; ++i) {
        Object obj = new byte [10*1024*1024];    // 10M
        SoftReference<Object> sr = new SoftReference<Object>(obj, q);
        referenceToId.put(sr, "soft:"+i);
        WeakReference<Object> wr = new WeakReference<Object>(obj, q);
        referenceToId.put(wr, "weak:"+i);

        for(;;){
            Reference<?> ref = q.poll();
            if(ref == null) {
                break;
            }
            System.out.println("cleared reference " + referenceToId.get(ref) + ", value=" + ref.get());
        }
    }
}
}

Jeśli uruchomisz go z -client lub-server, zobaczysz, że miękkie referencje są zawsze czyszczone przed słabymi referencjami, co również zgadza się z Javadoc: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability

Zazwyczaj miękkie / słabe odniesienia są używane w połączeniu z mapami do tworzenia rodzajów pamięci podręcznych. Jeśli klawisze na mapie są w porównaniu z operatorem==, (lub unoverriden .equals from Object), wtedy najlepiej użyć Map, która działa na klawiszach SoftReference (np. z Apache Commons) - gdy obiekt "zniknie"żaden inny obiekt nigdy nie będzie równy w sensie '==' do starego. Jeśli klawisze mapy są porównywane z zaawansowanymi .Operator equals (), podobnie jak String lub Date, wiele innych obiektów może pasować do 'znikającego', więc lepiej jest użyć standardowej WeakHashMap.

 0
Author: iirekm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-15 09:32:19

Zamiast tego spróbuj SoftReference. Javadoc mówi: wszystkie miękkie odniesienia do miękko osiągalnych obiektów są gwarantowane, że zostaną wyczyszczone, zanim maszyna wirtualna wyrzuci błąd OutOfMemoryError.

WeakReference nie ma takich gwarancji, co czyni je bardziej odpowiednimi dla buforów, ale czasami Softreferencje są lepsze.

 -1
Author: iirekm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-11 14:42:37