Dlaczego złą praktyką jest wywoływanie System.gc ()?


326

Po udzieleniu odpowiedzi na pytanie, w jaki sposób wymusić zwolnienie obiektów w Javie (facet wyczyścił HashMap o pojemności 1,5 GB) System.gc(), powiedziano mi, że wywoływanie System.gc()ręczne jest złe , ale komentarze nie były całkowicie przekonujące. Ponadto nikt nie odważył się głosować za moją odpowiedzią ani głosować za nią.

Powiedziano mi tam, że to zła praktyka, ale potem powiedziano mi również, że uruchamianie śmieciarza nie zatrzymuje już systematycznie świata, i że JVM może go skutecznie wykorzystać tylko jako podpowiedź, więc jestem rodzajem ze stratą.

Rozumiem, że JVM zwykle wie lepiej niż Ty, kiedy potrzebuje odzyskać pamięć. Rozumiem również, że martwienie się o kilka kilobajtów danych jest głupie. Rozumiem również, że nawet megabajty danych nie są tym, co było kilka lat temu. Ale nadal 1,5 gigabajta? I wiesz, że w pamięci kręci się około 1,5 GB danych; to nie jest strzał w ciemność. Czy jest System.gc()systematycznie zły, czy jest jakiś moment, w którym wszystko jest w porządku?

Pytanie jest więc podwójne:

  • Dlaczego dzwonienie jest lub nie jest złą praktyką System.gc()? Czy w niektórych implementacjach jest to jedynie wskazówka dla JVM, czy zawsze jest to pełny cykl zbierania? Czy istnieją naprawdę implementacje śmieciarek, które mogą wykonywać swoją pracę bez zatrzymywania świata? Proszę rzucić nieco światła na różne twierdzenia, które ludzie poczynili w komentarzach do mojej odpowiedzi .
  • Gdzie jest próg? Czy dzwonienie nigdy nie jest dobrym pomysłem System.gc(), czy są chwile, kiedy jest to do przyjęcia? Jeśli tak, to jakie są teraz czasy?

4
Myślę, że dobrym momentem na wywołanie System.gc () jest, gdy robisz coś, co jest już długim procesem ładowania. Na przykład pracuję nad grą i planuję wywołać System.gc (), gdy gra ładuje nowy poziom, pod koniec procesu ładowania. Użytkownik już trochę czeka, a dodatkowy wzrost wydajności może być tego wart; ale umieszczę również opcję na ekranie konfiguracji, aby wyłączyć to zachowanie.
Ricket

41
Bozho: zwróć uwagę na pierwsze słowo pytania. DLACZEGO najlepszą praktyką jest nie nazywać tego? Samo powtórzenie mantry nie wyjaśnia cholerstwa.
PO PROSTU MOJA poprawna OPINIA

1
Inny przypadek został wyjaśniony na stronie java-monitor.com/forum/showthread.php?t=188 Gdzie wyjaśniono, w jaki sposób wywoływanie System.gc () może być złą praktyką
Shirishkumar Bari

Odpowiedzi:


243

Powodem, dla którego wszyscy zawsze mówią, aby unikać, System.gc()jest to, że jest to całkiem dobry wskaźnik fundamentalnie zepsutego kodu . Każdy kod, który zależy od poprawności, jest z pewnością uszkodzony; wszystkie, które polegają na wydajności, najprawdopodobniej są zepsute.

Nie wiesz, pod jakim typem śmieciarza działasz. Z pewnością są takie, które nie „zatrzymują świata”, jak twierdzisz, ale niektóre maszyny JVM nie są tak inteligentne lub z różnych powodów (być może są na telefonie?) Tego nie robią. Nie wiesz co to zrobi.

Nie ma też gwarancji, że nic nie zrobisz. JVM może po prostu całkowicie zignorować twoją prośbę.

Kombinacja „nie wiesz, co to zrobi”, „nie wiesz, czy to w ogóle pomoże” i „nie powinieneś tego tak nazywać”, dlatego ludzie tak zdecydowanie mówią tak ogólnie nie powinieneś tego nazywać. Myślę, że jest to przypadek „jeśli musisz zapytać, czy powinieneś tego używać, nie powinieneś”


EDYCJA, aby rozwiązać kilka problemów z drugiego wątku:

Po przeczytaniu wątku, który połączyłeś, chciałbym zwrócić uwagę na jeszcze kilka rzeczy. Po pierwsze, ktoś zasugerował, że wywołanie gc()może zwrócić pamięć do systemu. To z pewnością niekoniecznie prawda - sama sterta Java rośnie niezależnie od przydziałów Java.

W miarę upływu czasu JVM będzie przechowywać pamięć (wiele dziesiątek megabajtów) i w razie potrzeby powiększy stertę. Niekoniecznie zwraca tę pamięć do systemu, nawet gdy zwalniasz obiekty Java; całkowicie za darmo można trzymać przydzieloną pamięć do wykorzystania w przyszłych alokacjach Java.

Aby pokazać, że możliwe jest, że System.gc()nic nie robi, zobacz:

http://bugs.sun.com/view_bug.do?bug_id=6668279

aw szczególności, że istnieje opcja -XX: DisableExplicitGC VM.


1
Być może uda ci się stworzyć dziwną konfigurację w stylu Rube Goldberga, w której metoda, w której działa GC, wpływa na poprawność twojego kodu. Być może maskuje jakieś dziwne interakcje wątków, a może finalizator ma znaczący wpływ na działanie programu. Nie jestem do końca pewien, czy to możliwe, ale może tak być, więc pomyślałem, że o tym wspomnę.
Steven Schlansker,

2
@zneak możesz na przykład umieścić krytyczny kod w finalizatorach (co jest zasadniczo zepsutym kodem)
Martin

3
Chciałbym dodać, że istnieje kilka narożnych przypadków, w których System.gc()jest to przydatne, a może nawet konieczne. Na przykład w aplikacjach interfejsu użytkownika w systemie Windows może znacznie przyspieszyć proces przywracania okna po wywołaniu System.gc () przed zminimalizowaniem okna (szczególnie gdy pozostaje ono zminimalizowane przez dłuższy czas, a części procesu zostają zamienione na dysk).
Joachim Sauer

2
@AndrewJanke Powiedziałbym, że kod, który używa WeakReferences dla obiektów, które chcesz zatrzymać, jest niepoprawny od samego początku, odśmiecania czy nie. Miałbyś ten sam problem w C ++ std::weak_ptr(choć możesz zauważyć ten problem w wersji C ++ wcześniej niż w wersji Java, ponieważ zniszczenie obiektu nie byłoby odraczane, jak zwykle jest finalizacja).
JAB

2
@rebeccah To błąd, więc tak, nazwałbym go „na pewno zepsuty”. Fakt, że System.gc()to naprawia, jest obejściem, a nie dobrą praktyką kodowania.
Steven Schlansker

149

Wyjaśniono już, że wywoływanie system.gc() może nic nie robić, a każdy kod, który „potrzebuje” modułu wyrzucającego elementy bezużyteczne, jest uszkodzony.

Jednak pragmatycznym powodem, dla którego jest to złą praktyką, System.gc()jest to, że jest nieefektywne. A w najgorszym przypadku jest to okropnie nieefektywne ! Pozwól mi wyjaśnić.

Typowy algorytm GC identyfikuje śmieci, przechodząc przez wszystkie nie-śmieciowe obiekty na stercie, i wnioskując, że każdy obiekt nie odwiedzony musi być śmieci. Na tej podstawie możemy modelować całkowitą pracę śmiecia składa się z jednej części, która jest proporcjonalna do ilości aktualnych danych, i drugiej części, która jest proporcjonalna do ilości śmieci; tj work = (live * W1 + garbage * W2).

Załóżmy teraz, że wykonujesz następujące czynności w aplikacji jednowątkowej.

System.gc(); System.gc();

Pierwsze połączenie (przewidujemy) (live * W1 + garbage * W2)zadziała i pozbędzie się zaległych śmieci.

Drugie połączenie (live* W1 + 0 * W2)zadziała i niczego nie odzyska. Innymi słowy, wykonaliśmy (live * W1)pracę i absolutnie nic nie osiągnęliśmy .

Możemy modelować efektywność kolektora jako ilość pracy potrzebnej do zebrania jednostki śmieci; tj efficiency = (live * W1 + garbage * W2) / garbage. Aby więc GC była jak najbardziej wydajna, musimy zmaksymalizować wartość, garbagekiedy uruchamiamy GC; tzn. poczekaj, aż sterty będą pełne. (A także, aby stos był jak największy. Ale to osobny temat.)

Jeśli aplikacja nie przeszkadza (przez wywołanie System.gc()), GC poczeka do uruchomienia sterty przed uruchomieniem, co spowoduje wydajne zbieranie śmieci 1 . Ale jeśli aplikacja zmusi GC do uruchomienia, istnieje duża szansa, że ​​sterty nie zostaną zapełnione, w wyniku czego śmieci będą zbierane nieefektywnie. Im częściej aplikacja wymusza GC, tym bardziej nieefektywna staje się GC.

Uwaga: powyższe objaśnienie przeskakuje nad faktem, że typowy współczesny GC dzieli stertę na „spacje”, GC może dynamicznie rozszerzać stertę, zestaw roboczy aplikacji nie-śmieciowych może się różnić i tak dalej. Mimo to ta sama podstawowa zasada dotyczy wszystkich prawdziwych śmieciarek 2 . Nieefektywne jest zmuszanie GC do uruchomienia.


1 - Tak działa kolektor „przepustowości”. Współbieżne kolektory, takie jak CMS i G1, używają różnych kryteriów, aby zdecydować, kiedy uruchomić moduł odśmiecający.

2 - Wykluczam także menedżerów pamięci, które używają wyłącznie zliczania referencji, ale żadna bieżąca implementacja Java nie używa tego podejścia ... nie bez powodu.


45
+1 Dobre wyjaśnienie. Pamiętaj jednak, że to rozumowanie ma zastosowanie tylko wtedy, gdy zależy Ci na przepustowości. Jeśli chcesz zoptymalizować ukrywanie w określonych punktach, wymuszenie GC może mieć sens. Np. (Hipotetycznie) w grze możesz unikać opóźnień podczas poziomów, ale nie przejmujesz się opóźnieniami podczas ładowania poziomów. Wtedy sensowne byłoby wymuszenie GC po obciążeniu poziomowym. Zmniejsza to ogólną przepustowość, ale nie to optymalizuje.
sleske,

2
@sleske - to, co mówisz, jest prawdą. Jednak uruchamianie GC między poziomami jest rozwiązaniem wspomagającym pasmo ... i nie rozwiązuje problemu opóźnienia, jeśli poziomy trwają wystarczająco długo, aby i tak trzeba było uruchomić GC podczas poziomu. Lepszym rozwiązaniem jest użycie równoczesnego (niskiego wstrzymania) modułu wyrzucania elementów bezużytecznych ... jeśli platforma to obsługuje.
Stephen C

Nie do końca wiadomo, co masz na myśli mówiąc „wymaga działania modułu wyrzucania elementów bezużytecznych”. Warunki, których należy unikać, to: niepowodzenia alokacji, których nigdy nie można zaspokoić i wysoki narzut GC. Wywoływanie losowe do System.gc () często powoduje wysoki narzut GC.
Kirk

@Kirk - „Losowe wykonywanie wywołań do System.gc () często powoduje wysokie obciążenie ogólne GC”. . Wiem to. Podobnie każdy, kto przeczytał i zrozumiał moją odpowiedź.
Stephen C

@Kirk - „Nie jestem całkowicie pewien, co masz na myśli ...” - Mam na myśli programy, w których „poprawne” zachowanie programu zależy od uruchomionej w danym momencie GC; np. w celu wykonania finalizatorów lub przerwania WeakReferences lub SoftReferences. To naprawdę zły pomysł, aby to zrobić ... ale o tym właśnie mówię. Zobacz także odpowiedź Stevena Schlanskera.
Stephen C

47

Wydaje się, że wiele osób mówi ci, abyś tego nie robił. Nie zgadzam się. Jeśli po długim procesie ładowania, takim jak ładowanie poziomu, uważasz, że:

  1. Masz wiele obiektów, które są nieosiągalne i mogły nie zostać zdobyte. i
  2. Myślisz, że użytkownik może znieść niewielkie spowolnienie w tym momencie

wywołanie System.gc () nie szkodzi. Patrzę na to jak na inlinesłowo kluczowe c / c ++ . To tylko wskazówka dla gc, że ty, deweloper, zdecydowałeś, że czas / wydajność nie jest tak ważna, jak zwykle, i że niektóre z nich można wykorzystać do odzyskania pamięci.

Porada, aby nie polegać na robieniu czegokolwiek, jest poprawna. Nie polegaj na tym, że działa, ale wskazanie, że teraz jest odpowiedni czas na zebranie, jest całkowicie w porządku. Wolę tracić czas w punkcie kodu, w którym nie ma to znaczenia (ekran ładowania), niż gdy użytkownik aktywnie wchodzi w interakcję z programem (np. Na poziomie gry).

Jest jeden czas, kiedy będę zmuszać kolekcję: gdy próbuje się dowiedzieć, jest szczególnym przecieki obiektów (zarówno natywny kod lub duże, złożone interakcje zwrotna No i każdy składnik UI, że tyle spojrzeń w Matlab.). To nigdy nie powinno być stosowane w kodzie produkcyjnym.


3
+1 dla GC podczas analizy wycieków pamięci. Zauważ, że informacje o użyciu sterty (Runtime.freeMemory () i in.) Są naprawdę znaczące tylko po wymuszeniu GC, w przeciwnym razie zależałoby to od tego, kiedy system ostatnio zadał sobie trud, aby uruchomić GC.
sleske,

4
wywołanie System.gc () nie szkodzi , może wymagać stop the worldpodejścia, a jeśli tak się stanie, to prawdziwa szkoda
bestsss

7
Nie ma nic złego w wywołaniu śmieciarza wyraźnie. Wywołanie GC w niewłaściwym czasie marnuje cykle procesora. Ty (programista) nie masz wystarczających informacji, aby określić, kiedy jest właściwy czas ... ale JVM ma.
Stephen C,

Jetty wykonuje 2 wywołania do System.gc () po uruchomieniu i rzadko widziałem, że robi to jakąkolwiek różnicę.
Kirk

Cóż, programiści Jetty mają błąd. To zrobiłoby różnicę ... nawet jeśli różnica jest trudna do oszacowania.
Stephen C

31

Ludzie wykonują dobrą robotę, tłumacząc, dlaczego NIE używać, więc powiem ci kilka sytuacji, w których powinieneś go używać:

(Poniższe komentarze dotyczą Hotspot działającego w systemie Linux z modułem zbierającym CMS, w którym jestem pewien, że System.gc()w rzeczywistości zawsze wywołuje pełne wyrzucanie elementów bezużytecznych).

  1. Po początkowej pracy związanej z uruchomieniem aplikacji możesz być w fatalnym stanie użycia pamięci. Połowa twojego pokolonego pokolenia może być pełna śmieci, co oznacza, że ​​jesteś o wiele bliżej swojego pierwszego CMS. W aplikacjach, w których ma to znaczenie, nie jest złym pomysłem wywołanie System.gc () w celu „zresetowania” sterty do początkowego stanu danych na żywo.

  2. Zgodnie z tymi samymi wierszami co numer 1, jeśli uważnie monitorujesz zużycie sterty, chcesz mieć dokładny odczyt tego, jakie jest twoje podstawowe użycie pamięci. Jeśli pierwsze 2 minuty czasu działania aplikacji to cała inicjalizacja, twoje dane zostaną pomieszane, chyba że wymusisz (hmm ... „zasugerowanie”) pełną gc z góry.

  3. Być może masz aplikację, która jest zaprojektowana tak, aby nigdy nie promować niczego w pokoleniu będącym w posiadaniu, gdy jest uruchomiona. Ale może trzeba zainicjować niektóre dane z góry, które nie są tak duże, aby automatycznie przenieść się do pokrewnej generacji. O ile nie wywołamy System.gc () po skonfigurowaniu wszystkiego, dane mogą znajdować się w nowej generacji, dopóki nie nadejdzie czas na awans. Nagle twoja super-duperowa aplikacja o niskim opóźnieniu i niskiej GC zostaje uderzona OGROMNĄ (względnie rzecz biorąc, oczywiście) opóźnieniem za promowanie tych obiektów podczas normalnych operacji.

  4. Czasami przydaje się udostępnienie wywołania System.gc w aplikacji produkcyjnej do weryfikacji istnienia wycieku pamięci. Jeśli wiesz, że zestaw danych na żywo w czasie X powinien istnieć w pewnym stosunku do zestawu danych na żywo w czasie Y, wówczas przydatne może być wywołanie System.gc () czasu X i czasu Y i porównanie użycia pamięci .


1
W przypadku większości generatorów śmieciarek obiekty nowej generacji muszą przetrwać określoną (często konfigurowalną) liczbę śmieci, więc System.gc()jednorazowe wywołanie w celu wymuszenia promocji obiektów nic ci nie da. I na pewno nie chcesz dzwonić System.gc()osiem razy z rzędu i modlić się, aby teraz promocja została zakończona, a zaoszczędzone koszty późniejszej promocji uzasadniają koszty wielu pełnych GC. W zależności od algorytmu GC promowanie wielu obiektów może nawet nie ponosić faktycznych kosztów, ponieważ po prostu ponownie przypisuje pamięć do starej generacji lub kopiuje jednocześnie…
Holger

11

To bardzo kłopotliwe pytanie i wydaje mi się, że wiele osób sprzeciwia się Javie, pomimo tego, jak użyteczny jest ten język.

Fakt, że nie można ufać, że „System.gc” zrobi cokolwiek, jest niezwykle zniechęcający i łatwo wywołuje w języku uczucie „strachu, niepewności, wątpliwości”.

W wielu przypadkach miło jest radzić sobie ze skokami pamięci, które celowo wywołujesz, zanim nastąpi ważne zdarzenie, które sprawi, że użytkownicy będą myśleć, że Twój program jest źle zaprojektowany / nie odpowiada.

Umiejętność kontrolowania wyrzucania elementów bezużytecznych byłaby bardzo doskonałym narzędziem edukacyjnym, co z kolei poprawiłoby zrozumienie ludzi, jak działa wyrzucanie elementów bezużytecznych i jak programy wykorzystują to zachowanie domyślne i kontrolowane.

Pozwól mi przejrzeć argumenty tego wątku.

  1. Jest nieefektywny:

Często program może nic nie robić i wiesz, że nic nie robi ze względu na sposób, w jaki został zaprojektowany. Na przykład może to być coś w rodzaju długiego oczekiwania z dużym oknem komunikatu oczekiwania, a na końcu równie dobrze może dodać wezwanie do zbierania śmieci, ponieważ czas jego uruchomienia zajmie naprawdę niewielki ułamek czasu długo czekać, ale pozwoli uniknąć działania gc w trakcie ważniejszej operacji.

  1. Jest to zawsze zła praktyka i wskazuje na uszkodzony kod.

Nie zgadzam się, nie ma znaczenia, jaki posiadasz śmieci. Jego zadaniem jest śledzenie śmieci i ich czyszczenie.

Dzwoniąc do gc w czasach, gdy użycie jest mniej krytyczne, zmniejszasz prawdopodobieństwo jego uruchomienia, gdy twoje życie zależy od uruchomionego określonego kodu, ale zamiast tego decyduje się na zbieranie śmieci.

Jasne, może nie zachowywać się tak, jak tego chcesz lub się spodziewasz, ale kiedy chcesz to nazwać, wiesz, że nic się nie dzieje, a użytkownik jest skłonny tolerować spowolnienie / przestoje. Jeśli plik System.gc działa, świetnie! Jeśli nie, przynajmniej spróbowałeś. Po prostu nie ma wad, chyba że śmieciarz ma nieodłączne skutki uboczne, które powodują coś okropnie nieoczekiwanego w stosunku do zachowania się śmieciarza, jeśli zostanie wywołany ręcznie, a to samo w sobie powoduje brak zaufania.

  1. To nie jest powszechny przypadek użycia:

Jest to przypadek użycia, który nie może być osiągnięty w sposób niezawodny, ale mógłby mieć miejsce, gdyby system został zaprojektowany w ten sposób. To tak, jakby zrobić sygnalizację świetlną i sprawić, aby niektóre / wszystkie przyciski sygnalizacji świetlnej nic nie robiły. To sprawia, że ​​zastanawiasz się, dlaczego ten przycisk jest na początek, javascript nie ma funkcji usuwania śmieci, więc nie nie analizuj tego za to.

  1. Specyfikacja mówi, że System.gc () jest wskazówką, że GC powinien uruchomić, a maszyna wirtualna może go zignorować.

co to jest „wskazówka”? co to jest „ignorować”? komputer nie może po prostu przyjmować wskazówek ani niczego ignorować, istnieją ścisłe ścieżki zachowania, które mogą być dynamiczne, które są kierowane intencją systemu. Prawidłowa odpowiedź obejmowałaby to, co tak naprawdę robi śmieciarz, na poziomie implementacji, co powoduje, że nie wykonuje on zbierania danych na żądanie. Czy ta funkcja jest po prostu niedostępna? Czy są jakieś warunki, które muszę spełnić? Jakie są te warunki?

W tej chwili GC Javy często wygląda jak potwór, któremu po prostu nie ufasz. Nie wiesz, kiedy to przyjdzie lub odejdzie, nie wiesz, co się stanie, jak to zrobi. Mogę sobie wyobrazić, że niektórzy eksperci mają lepsze pojęcie o tym, jak działa ich odśmiecanie według instrukcji, ale zdecydowana większość po prostu ma nadzieję, że „po prostu działa”, i że trzeba ufać nieprzejrzystemu algorytmowi, który wykona pracę dla ciebie, jest frustrujący.

Istnieje duża luka między czytaniem o czymś lub uczeniem się czegoś, a faktycznym widzeniem jego realizacji, różnicami między systemami i możliwością grania bez konieczności patrzenia na kod źródłowy. To stwarza pewność siebie i poczucie opanowania / zrozumienia / kontroli.

Podsumowując, istnieje nieodłączny problem z odpowiedziami: „ta funkcja może nic nie robić i nie będę wchodził w szczegóły, jak powiedzieć, kiedy coś robi, a kiedy nie, i dlaczego nie zrobi lub nie, często sugeruje, że próba zrobienia tego jest po prostu sprzeczna z filozofią, nawet jeśli intencja jest uzasadniona ”.

Może być w porządku, aby Java GC zachowywała się w ten sposób, lub nie, ale aby to zrozumieć, trudno jest naprawdę podążać w jakim kierunku, aby uzyskać kompleksowy przegląd tego, co możesz zaufać GC i nie robić, więc zbyt łatwo jest po prostu nie ufać językowi, ponieważ celem języka jest kontrolowane zachowanie do filozoficznego stopnia (dla programisty, szczególnie początkującego, łatwo popadnie w kryzys egzystencjalny z powodu pewnych zachowań systemowych / językowych) są w stanie tolerować (a jeśli nie możesz, po prostu nie będziesz używać języka, dopóki nie będziesz musiał), a więcej rzeczy, których nie możesz kontrolować bez żadnego znanego powodu, dla którego nie możesz ich kontrolować, jest z natury szkodliwe.


9

Wydajność GC opiera się na szeregu heurystyk. Na przykład powszechna heurystyka polega na tym, że dostęp do zapisu obiektów występuje zwykle na obiektach, które zostały utworzone niedawno. Innym jest to, że wiele obiektów jest bardzo krótkotrwałych (niektóre będą używane przez długi czas, ale wiele zostanie odrzuconych kilka mikrosekund po ich utworzeniu).

Dzwonienie System.gc()jest jak kopanie GC. Oznacza to: „wszystkie te dokładnie dostrojone parametry, te inteligentne organizacje, cały wysiłek włożony w przydzielanie i zarządzanie obiektami w taki sposób, aby wszystko szło gładko, no cóż, po prostu zrzuć wszystko i zacznij od zera”. To może zwiększyć wydajność, ale przez większość czasu to tylko pogarsza osiągi.

Aby korzystać System.gc()niezawodnie (*), musisz wiedzieć, jak działa GC we wszystkich jego drobnych szczegółach. Takie szczegóły mogą się nieco zmienić, jeśli używasz JVM od innego dostawcy lub następnej wersji od tego samego dostawcy lub tego samego JVM, ale z nieco innymi opcjami wiersza polecenia. Dlatego rzadko jest to dobry pomysł, chyba że chcesz rozwiązać konkretny problem, w którym kontrolujesz wszystkie te parametry. Stąd pojęcie „złej praktyki”: nie jest to zabronione, metoda istnieje, ale rzadko się opłaca.

(*) Mówię tutaj o wydajności. System.gc()nigdy nie zepsuje poprawnego programu Java. Nie wyczaruje dodatkowej pamięci, której JVM nie mógłby uzyskać inaczej: przed wyrzuceniem OutOfMemoryError, JVM wykonuje zadanie System.gc(), nawet jeśli w ostateczności.


1
+1 za wspomnienie, że System.gc () nie zapobiega OutOfMemoryError. Niektórzy ludzie w to wierzą.
sleske,

1
W rzeczywistości może zapobiec OutOfMemoryError z powodu obsługi miękkich odniesień. SoftRefereferencje utworzone po ostatnim uruchomieniu GC nie są gromadzone w implementacji, którą znam. Jest to jednak szczegół implementacji, który może ulec zmianie w dowolnym momencie, rodzaj błędu i nic, na czym można polegać.
maaartinus

8

Czasami ( nie często! ) Naprawdę wiesz więcej na temat przeszłego, obecnego i przyszłego wykorzystania pamięci niż czas działania. Nie zdarza się to zbyt często i twierdziłbym, że nigdy w aplikacji internetowej, gdy są obsługiwane normalne strony.

Wiele lat temu pracowałem nad generatorem raportów

  • Miałem jeden wątek
  • Przeczytaj „zgłoszenie żądania” z kolejki
  • Załadowano dane potrzebne do raportu z bazy danych
  • Wygenerowałem raport i wysłałem go e-mailem.
  • Powtarzane na zawsze, śpiące, gdy nie było żadnych zaległych próśb.
  • Nie wykorzystał ponownie żadnych danych między raportami i nie dokonał żadnych płatności.

Po pierwsze, ponieważ nie był to czas rzeczywisty, a użytkownicy oczekiwali na zgłoszenie, opóźnienie w uruchomieniu GC nie stanowiło problemu, ale musieliśmy tworzyć raporty w tempie szybszym niż oczekiwano.

Patrząc na powyższy zarys procesu, jasne jest, że.

  • Wiemy, że po wysłaniu raportu e-mail będzie niewiele obiektów na żywo, ponieważ następne żądanie nie zostało jeszcze przetworzone.
  • Powszechnie wiadomo, że koszt uruchomienia cyklu usuwania śmieci zależy od liczby obiektów aktywnych, ilość śmieci ma niewielki wpływ na koszt uruchomienia GC.
  • Gdy kolejka jest pusta, nie ma nic lepszego do roboty niż uruchomienie GC.

Dlatego wyraźnie warto było wykonywać GC, gdy kolejka żądań była pusta; nie było w tym żadnych wad.

Może warto wykonać test GC po wysłaniu każdego raportu pocztą e-mail, ponieważ wiemy, że jest to dobry czas na test GC. Jeśli jednak komputer ma wystarczającą ilość pamięci RAM, lepsze wyniki można uzyskać, opóźniając uruchomienie GC.

To zachowanie zostało skonfigurowane dla poszczególnych instalacji, dla niektórych klientów włączenie wymuszonej GC po każdym raporcie znacznie przyspieszyło ochronę raportów. (Spodziewam się, że było to spowodowane niską pamięcią na serwerze i działaniem wielu innych procesów, dlatego też wymuszone GC zredukowało stronicowanie.)

Nigdy nie wykryliśmy, że instalacja, która nie przyniosła korzyści, była wymuszonym uruchomieniem GC za każdym razem, gdy kolejka robocza była pusta.

Ale, powiedzmy jasno, powyższe nie jest częstym przypadkiem.


3

Może piszę kiepski kod, ale zdałem sobie sprawę, że kliknięcie ikony kosza na IDE zaćmienia i siatki jest „dobrą praktyką”.


1
Może tak być. Ale jeśli Eclipse lub NetBeans zostały zaprogramowane do System.gc()okresowego wywoływania , prawdopodobnie byłoby to denerwujące.
Stephen C

2

Po pierwsze, istnieje różnica między specyfikacją a rzeczywistością. Specyfikacja mówi, że System.gc () jest wskazówką, że GC powinien uruchomić, a maszyna wirtualna może go zignorować. W rzeczywistości maszyna wirtualna nigdy nie zignoruje wywołania System.gc ().

Dzwonienie do GC wiąże się z nietrywialnym kosztem połączenia, a jeśli zrobisz to w przypadkowym momencie, prawdopodobnie nie zobaczysz nagrody za swoje wysiłki. Z drugiej strony, naturalnie wywołana kolekcja najprawdopodobniej zwróci koszty połączenia. Jeśli masz informacje wskazujące, że należy uruchomić GC, możesz zadzwonić do System.gc () i powinieneś zobaczyć korzyści. Jednak z mojego doświadczenia wynika, że ​​dzieje się tak tylko w kilku przypadkach, ponieważ jest bardzo mało prawdopodobne, że będziesz mieć wystarczającą ilość informacji, aby zrozumieć, czy i kiedy należy wywołać System.gc ().

Jeden z wymienionych tutaj przykładów, uderzanie w kosz na śmieci w twoim IDE. Jeśli idziesz na spotkanie, dlaczego by nie trafić? Koszty ogólne nie będą miały na ciebie wpływu, a sterty mogą zostać oczyszczone po powrocie. Zrób to w systemie produkcyjnym, a częste wezwania do odbioru zatrzymają go! Nawet okazjonalne połączenia takie jak te wykonywane przez RMI mogą zakłócać wydajność.


3
„W rzeczywistości maszyna wirtualna nigdy nie zignoruje wywołania System.gc ().” - Niepoprawnie. Przeczytaj o -XX: -DisableExplicitGC.
Stephen C

1

Tak, wywołanie System.gc () nie gwarantuje, że się uruchomi, jest to zapytanie do JVM, które można zignorować. Z dokumentów:

Wywołanie metody gc sugeruje, że wirtualna maszyna Java poświęci wysiłek na recykling nieużywanych obiektów

Nazywanie go prawie zawsze jest złym pomysłem, ponieważ automatyczne zarządzanie pamięcią zwykle wie lepiej niż ty, kiedy masz gc. Dzieje się tak, gdy w wewnętrznej puli wolnej pamięci jest mało lub jeśli system operacyjny zażąda zwrotu części pamięci.

Dopuszczalne może być wywołanie System.gc (), jeśli wiesz, że to pomaga. Rozumiem przez to, że dokładnie przetestowałeś i zmierzyłeś zachowanie obu scenariuszy na platformie wdrażania , i możesz pokazać, że to pomaga. Pamiętaj jednak, że gc nie jest łatwo przewidywalny - może pomóc w jednym biegu, a zaszkodzić w innym.


<stroke> Ale także z Javadoc: _Kiedy kontrola powraca z wywołania metody, maszyna wirtualna dołożyła wszelkich starań, aby przetworzyć wszystkie odrzucone obiekty, co uważam za bardziej imperatywną formę tego, o czym pisałeś. </ stroke > Chrzanić, jest raport o błędzie, który wprowadza w błąd. Które z nich wie lepiej, jakie są szkody wskazujące na JVM?
zneak

1
Szkoda, że ​​zbieranie w niewłaściwym czasie może być ogromnym spowolnieniem. Wskazówka, którą dajesz, jest prawdopodobnie zła. Jeśli chodzi o komentarz dotyczący „najlepszego wysiłku”, spróbuj go i zobacz w narzędziu takim jak JConsole. Czasami kliknięcie przycisku „Wykonaj GC” nic nie robi
tom

Przykro nam, że się nie zgadzam, ale wywołanie System.gc () w OpenJDK i wszystko, co jest na nim oparte (na przykład HP) zawsze powoduje cykl wyrzucania elementów bezużytecznych. W rzeczywistości wydaje się to prawdą również w przypadku implementacji J9 przez IBM
Kirk

@Kirk - Niepoprawnie: google i poczytać o -XX: -DisableExplicitGC.
Stephen C

0

Z mojego doświadczenia wynika, że ​​używanie System.gc () jest efektywną formą optymalizacji specyficzną dla platformy (gdzie „platforma” to kombinacja architektury sprzętowej, systemu operacyjnego, wersji JVM i możliwych większej liczby parametrów środowiska wykonawczego, takich jak dostępna pamięć RAM), ponieważ jej zachowanie, chociaż z grubsza przewidywalne na konkretnej platformie, może (i będzie) znacznie się różnić między platformami.

Tak, sytuacje, w których System.gc () poprawi (postrzega) wydajność. Na przykład, jeśli opóźnienia są dopuszczalne w niektórych częściach twojej aplikacji, ale nie w innych (przykład gry cytowany powyżej, w którym chcesz, aby GC miało miejsce na początku poziomu, a nie na poziomie).

Jednak to, czy to pomoże, czy skrzywdzi (lub nic nie zrobi), zależy w dużej mierze od platformy (jak zdefiniowano powyżej).

Myślę więc, że jest to poprawna optymalizacja specyficzna dla platformy w ostateczności (tzn. Jeśli inne optymalizacje wydajności nie są wystarczające). Ale nigdy nie powinieneś tego nazywać tylko dlatego, że uważasz, że to może pomóc (bez określonych testów porównawczych), ponieważ są szanse, że nie będzie.


0
  1. Ponieważ obiekty są przydzielane dynamicznie za pomocą nowego operatora,
    możesz się zastanawiać, jak takie obiekty są niszczone i ich
    pamięć zwalniana w celu późniejszego ponownego przydzielenia.

  2. W niektórych językach, takich jak C ++, dynamicznie przydzielane obiekty muszą być ręcznie zwalniane za pomocą operatora delete.

  3. Java ma inne podejście; automatycznie zajmuje się zwolnieniem.
  4. Technika, która to osiąga, nosi nazwę odśmiecania. Działa to w ten sposób: gdy nie istnieją żadne odniesienia do obiektu, zakłada się, że obiekt ten nie jest już potrzebny, a pamięć zajmowaną przez obiekt można odzyskać. Nie ma wyraźnej potrzeby niszczenia obiektów jak w C ++.
  5. Odśmiecanie występuje tylko sporadycznie (jeśli w ogóle) podczas wykonywania programu.
  6. Nie nastąpi to po prostu dlatego, że istnieje jeden lub więcej obiektów, które nie są już używane.
  7. Co więcej, różne implementacje Java w czasie wykonywania będą przyjmować różne podejścia do wyrzucania elementów bezużytecznych, ale w większości przypadków nie powinieneś o tym myśleć podczas pisania programów.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.