Pytanie Tworzenie wycieku pamięci za pomocą Java


Właśnie miałem wywiad i poproszono mnie o utworzenie wycieku pamięci w Javie. Nie trzeba dodawać, że czułem się dość głupi, nie mając pojęcia, jak zacząć go tworzyć.

Jaki byłby przykład?


2641


pochodzenie


Powiedziałbym im, że Java używa narzędzia do zbierania śmieci i poprosi ich o nieco dokładniejszą definicję "wycieku pamięci", wyjaśniając, że - z wyjątkiem błędów JVM - Java nie może przeciekać pamięci całkiem w ten sam sposób, co C / C ++. Musisz mieć odniesienie do obiektu gdzieś. - Darien
Uważam, że to zabawne, że w większości przypadków ludzie szukają tych skrajnych przypadków i sztuczek, i wydaje się, że kompletnie ich brakuje (IMO). Mogliby po prostu pokazać kod, który utrzymywałby bezużyteczne odniesienia do obiektów, które nigdy nie będą używane ponownie, a jednocześnie nigdy nie usuwają tych odniesień; Można powiedzieć, że te przypadki nie są "prawdziwymi" wyciekami pamięci, ponieważ wciąż istnieją odniesienia do tych obiektów, ale jeśli program nigdy nie użyje tych odniesień ponownie, a także nigdy ich nie upuści, jest całkowicie równoważny (i tak zły, jak) " prawdziwy wyciek pamięci ". - ehabkost
Szczerze mówiąc, nie mogę uwierzyć, że podobne pytanie, które zadałam sobie na temat "Go", zostało obniżone do -1. Tutaj: stackoverflow.com/questions/4400311/...   Zasadniczo wycieki pamięci, o których mówiłem, to te, które otrzymały +200 punktów do PO, a jednak zostałem zaatakowany i znieważony za pytanie, czy "Go" miał ten sam problem. Jakoś nie jestem pewien, czy wszystkie wiki-rzeczy działają tak świetnie. - SyntaxT3rr0r
@ SyntaxT3rr0r - odpowiedź Dariena nie jest fanboyizmem. Wyraźnie przyznał, że niektóre JVM mogą mieć błędy, które oznaczają wyciek pamięci. to jest inne niż sama specyfikacja językowa pozwalająca na wycieki pamięci. - Peter Recore
@ehabkost: Nie, nie są one równoważne. (1) Masz możliwość odzyskania pamięci, podczas gdy w "prawdziwym przecieku" twój program C / C ++ zapomina o przydzielonym zakresie, nie ma bezpiecznej drogi do odzyskania. (2) Możesz bardzo łatwo wykryć problem z profilowaniem, ponieważ możesz zobaczyć, jakie obiekty obejmuje "rozdęcie". (3) "Prawdziwy wyciek" jest jednoznacznym błędem, podczas gdy program, który utrzymuje wiele obiektów do momentu zakończenia mógłby być zamierzoną częścią tego, jak ma działać. - Darien


Odpowiedzi:


Oto dobry sposób na stworzenie prawdziwego wycieku pamięci (obiekty niedostępne przez uruchomienie kodu, ale nadal przechowywane w pamięci) w czystej Java:

  1. Aplikacja tworzy długo działający wątek (lub używa puli wątków, aby przeciekać jeszcze szybciej).
  2. Wątek ładuje klasę za pomocą (opcjonalnie niestandardowej) ClassLoader.
  3. Klasa przydziela dużą porcję pamięci (np. new byte[1000000]), przechowuje silne odwołanie do niego w polu statycznym, a następnie zapisuje odniesienie do niego w wątku LocalLocal. Przydzielenie dodatkowej pamięci jest opcjonalne (przecieka instancja klasy jest wystarczająca), ale spowoduje to, że wyciek będzie działał o wiele szybciej.
  4. Wątek usuwa wszystkie odwołania do niestandardowej klasy lub klasy ClassLoader, z której został załadowany.
  5. Powtarzać.

Działa to, ponieważ ThreadLocal zachowuje odwołanie do obiektu, który zachowuje odwołanie do swojej klasy, która z kolei zachowuje odwołanie do swojej klasy ładującej. Z kolei ClassLoader przechowuje odniesienie do wszystkich załadowanych klas.

(Było gorzej w wielu implementacjach JVM, zwłaszcza przed Java 7, ponieważ klasy i classLoaders zostały przydzielone wprost do permgen i nigdy nie były w ogóle generowane GC, jednak niezależnie od tego, w jaki sposób JVM radzi sobie z rozładowywaniem klasy, ThreadLocal nadal uniemożliwia Obiekt klasy nie jest odzyskiwany.)

Odmianą tego wzorca jest to, dlaczego kontenery aplikacji (takie jak Tomcat) mogą przeciekać pamięć jak sito, jeśli często ponownie wdrażasz aplikacje, które używają ThreadLocals w jakikolwiek sposób. (Ponieważ kontener aplikacji używa wątków zgodnie z opisem, a przy każdym ponownym wdrażaniu aplikacji używana jest nowa klasa ClassLoader.)

Aktualizacja: Ponieważ wiele osób wciąż o to pyta, oto przykład kodu, który pokazuje to zachowanie w akcji.


1935



+1 Wycieki ClassLoadera to jedne z najczęstszych bolesnych wycieków pamięci w świecie JEE, często spowodowane przez biblioteki firm trzecich, które przekształcają dane (BeanUtils, kodeki XML / JSON). Może się to zdarzyć, gdy biblioteka lib zostanie załadowana poza główny program ładujący klasy aplikacji, ale zawiera odniesienia do twoich klas (np. Przez buforowanie). Po odinstalowaniu / ponownym wdrożeniu aplikacji JVM nie może wyrzucać śmieci w celu pobrania programu ładującego klasy (a więc wszystkich klas ładowanych przez niego), więc przy powtórnym wdrażaniu serwer aplikacji ostatecznie borks. Jeśli masz szczęście, możesz uzyskać wskazówkę z ClassCastException z.x.y.Abc nie można przesłać na adres z.x.y.Abc - earcam
tomcat używa trików i nils WSZYSTKIE zmienne statyczne we WSZYSTKICH załadowanych klasach, tomcat ma dużo datariaces i złe kodowanie (potrzebuję trochę czasu i przesłać poprawki), a także wszystkie zadziwiające ConcurrentLinkedQueue jako pamięć podręczną dla wewnętrznych (małych) obiektów, tak mały, że nawet ConcurrentLinkedQueue.Node zabiera więcej pamięci. - bestsss
+1: przecieki Classloadera to koszmar. Spędziłem tygodnie, próbując je rozgryźć. Smutne jest to, co powiedział @earcam, są one głównie spowodowane przez biblioteki stron trzecich, a także większość profilerów nie może wykryć tych wycieków. Na tym blogu jest dobre i jasne wytłumaczenie wycieków Classloadera. blogs.oracle.com/fkieviet/entry/... - Adrian M
@Nicolas: Jesteś pewien? JRockit domyślnie robi obiekty klasy GC, a HotSpot nie, ale AFAIK JRockit nadal nie może GC klasy ani ClassLoader, do której odwołuje się ThreadLocal. - Daniel Pryden
Tomcat spróbuje wykryć te wycieki i ostrzegać o nich: wiki.apache.org/tomcat/MemoryLeakProtection. Najnowsza wersja czasami nawet naprawi wyciek. - Matthijs Bierman


Statyczne odwołanie do obiektu trzymającego pole [szczególnie pole końcowe]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Powołanie String.intern() na długim łańcuchu

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Unclosed) otwarte strumienie (plik, sieć itp ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Niezamknięte połączenia

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Obszary, które nie są dostępne ze śmieciarza JVM, takich jak pamięć przydzielona za pomocą metod natywnych

W aplikacjach internetowych niektóre obiekty są przechowywane w zakresie aplikacji do momentu jawnego zatrzymania lub usunięcia aplikacji.

getServletContext().setAttribute("SOME_MAP", map);

Nieprawidłowe lub niewłaściwe opcje maszyny JVM, tak jak noclassgc opcja na IBM JDK, która zapobiega nieużywanemu zbieraniu śmieci klasy

Widzieć Ustawienia IBM jdk.


1062



Nie zgadzam się z tym, że kontekst i atrybuty sesji to "przecieki". To tylko zmienne długożyciowe. A statyczne pole końcowe jest mniej więcej stałe. Być może należy unikać dużych stałych, ale uważam, że nie można tego nazwać wyciekiem pamięci. - Ian McLaird
(Unclosed) otwarte strumienie (plik, sieć itp ...), nie przecieka naprawdę, podczas finalizacji (która nastąpi po następnym cyklu GC) close () zostanie zaplanowane (close() zazwyczaj nie jest wywoływany w wątku finalizatora, ponieważ może to być operacja blokowania). Zła praktyka nie zamykać, ale nie powoduje przecieku. Unclosed java.sql.Connection jest taki sam. - bestsss
W większości rozsądnych maszyn JVM wygląda na to, że klasa String ma słabe odniesienie do jej intern zawartość hashtable. Jako taki, to jest śmieci zebrane prawidłowo, a nie wyciek. (ale IANAJP) mindprod.com/jgloss/interned.html#GC - Matt B.
Jak statyczne odwołanie do obiektu pola gospodarstwa [szczególnie w polu końcowym] jest wyciek pamięci? - Kanagavelu Sugumar
@ cHao True. Niebezpieczeństwo, które napotkam, nie jest problemem z powodu wycieku pamięci przez strumienie. Problem polega na tym, że nie ma w nich wystarczająco dużo pamięci. Możesz przeciekać wiele uchwytów, ale wciąż masz dużo pamięci. Garbage Collector może wtedy zdecydować, aby nie zajmować się całą kolekcją, ponieważ wciąż ma dużo pamięci. Oznacza to, że finalizator nie jest wywoływany, więc zabraknie uchwytów. Problem polega na tym, że finalizatory będą (zwykle) uruchamiane, zanim zabraknie pamięci z nieszczelnych strumieni, ale może nie zostać wywołana, zanim zabraknie czegoś innego niż pamięć. - Patrick M


Prostą rzeczą jest użycie HashSet z niepoprawnym (lub nieistniejącym) hashCode() lub equals(), a następnie dodawaj "duplikaty". Zamiast ignorować duplikaty, tak jak powinno, zestaw będzie się tylko powiększał i nie będzie można ich usunąć.

Jeśli chcesz, aby te złe klucze / elementy były zawieszone, możesz użyć statycznego pola, takiego jak

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

390



Właściwie możesz usunąć elementy z HashSet, nawet jeśli klasa elementów dostaje hashCode i jest równa; po prostu pobierz iterator dla zestawu i użyj jego metody usuwania, ponieważ iterator faktycznie działa na samych wejściach, a nie na elementach. (Należy zauważyć, że niezmodyfikowany kod haszowania / równy jest nie wystarczająco, aby spowodować wyciek; ustawienia domyślne implementują prostą tożsamość obiektu, dzięki czemu można uzyskać elementy i usunąć je normalnie.) - Donal Fellows
@Donal co próbuję powiedzieć, myślę, że nie zgadzam się z twoją definicją wycieku pamięci. Zastanowiłbym się (kontynuując analogię), że twoja technika usuwania iteratora jest kroplówką pod przeciekiem; przeciek nadal istnieje niezależnie od tacki ociekowej. - corsiKa
Zgadzam się, to jest nie "wyciek" pamięci, ponieważ możesz po prostu usunąć odniesienia do hashset i poczekać, aż GC się uruchomi, a presto! pamięć wraca. - Mehrdad
@ SyntaxT3rr0r, zinterpretowałem twoje pytanie jako pytające, czy jest coś w języku, co naturalnie prowadzi do wycieków pamięci. Odpowiedź brzmi nie. To pytanie zadaje pytanie, czy można wymyślić sytuację, aby stworzyć coś takiego jak wyciek pamięci. Żaden z tych przykładów nie jest przeciekiem pamięci w sposób, który mógłby go zrozumieć programista C / C ++. - Peter Lawrey
@ Peter Lawrey: co myślisz o tym: "W języku C nie ma nic, co naturalnie przecieka do wycieku pamięci, jeśli nie zapomnisz ręcznie zwolnić przydzielonej pamięci". Jak by to było z intelektualną nieuczciwością? W każdym razie jestem zmęczony: możesz mieć ostatnie słowo. - SyntaxT3rr0r


Poniżej będzie nieoczywisty przypadek, w którym Java wycieknie, oprócz standardowego przypadku zapomnianych słuchaczy, odwołań statycznych, fałszywych / modyfikowalnych kluczy w hashmapach, lub tylko wątków utkniętych bez szansy na zakończenie ich cyklu życia.

  • File.deleteOnExit()- zawsze przecieka sznur, jeśli łańcuch jest podłańcuchem, przeciek jest jeszcze gorszy (ukryty char [] również wyciekł) - w podfolderze Java 7 kopiuje również char[], więc później nie ma zastosowania; @Daniel, nie ma potrzeby głosowania.

Skoncentruję się na wątkach, aby pokazać przede wszystkim niebezpieczeństwo niezarządzanych wątków, nie chcę nawet dotykać huśtawki.

  • Runtime.addShutdownHook i nie usuwać ... a nawet przy użyciu removeShutdownHook z powodu błędu w klasie ThreadGroup dotyczącego niezałatowanych wątków, może nie zostać zebrany, skutecznie przecieka grupa ThreadGroup. JGroup ma przeciek w GossipRouter.

  • Tworzenie, ale nie uruchamianie, a Thread przechodzi do tej samej kategorii, co powyżej.

  • Tworzenie wątku dziedziczy ContextClassLoader i AccessControlContext, plus ThreadGroup i jakikolwiek InheritedThreadLocalwszystkie te referencje są potencjalnymi przeciekami, podobnie jak wszystkie klasy ładowane przez program ładujący klasy i wszystkie odwołania statyczne, a także ja-ja. Efekt jest szczególnie widoczny w całej strukturze j.u.c.Executor, która cechuje się superprostością ThreadFactory interfejs, ale większość programistów nie ma pojęcia o niebezpieczeństwie czającym się w sieci. Również wiele bibliotek uruchamia wątki na żądanie (zbyt wiele popularnych bibliotek branżowych).

  • ThreadLocal pamięci podręczne; w wielu przypadkach są to zło. Jestem pewien, że wszyscy widzieli sporo prostych pamięci podręcznych opartych na wątku ThreadLocal, a także zła wiadomość: jeśli wątek będzie kontynuował więcej niż oczekiwano życie w kontekście ClassLoader, jest to czysty, miły mały wyciek. Nie używaj pamięci podręcznych ThreadLocal, chyba że naprawdę jest to potrzebne.

  • Powołanie ThreadGroup.destroy() gdy ThreadGroup nie ma wątków jako takich, ale nadal utrzymuje potomne ThreadGroups. Zły wyciek, który uniemożliwi Grupie ThreadGroup usunięcie jej z rodzica, ale wszystkie dzieci stają się niewymienialne.

  • Użycie WeakHashMap i wartość (in) bezpośrednio odwołuje się do klucza. Jest to trudne do znalezienia bez zrzutu sterty. Dotyczy to wszystkich rozszerzonych Weak/SoftReference to może zachować twardy odnośnik z powrotem do strzeżonego obiektu.

  • Za pomocą java.net.URL z protokołem HTTP (S) i ładowaniem zasobu z (!). Ten jest wyjątkowy, KeepAliveCache tworzy nowy wątek w systemie ThreadGroup, który przecieka moduł ładujący kontekstowego bieżącego wątku. Wątek jest tworzony przy pierwszym żądaniu, gdy nic nie istnieje, więc możesz mieć szczęście lub po prostu przeciekać. Wyciek został już naprawiony w Javie 7, a kod, który tworzy wątek, poprawnie usuwa moduł ładujący klasy. Istnieje kilka innych przypadków (jak ImageFetcher, również naprawione) tworzenia podobnych wątków.

  • Za pomocą InflaterInputStream przechodzący new java.util.zip.Inflater() w konstruktorze (PNGImageDecoder na przykład) i nie dzwonić end() nadmuchującego. Cóż, jeśli przejdziesz w konstruktorze za pomocą just newnie ma szans ... I tak, dzwonienie close() w strumieniu nie zamyka nadmuchiwacza, jeśli został ręcznie przekazany jako parametr konstruktora. To nie jest prawdziwy wyciek, ponieważ zostałby zwolniony przez finalizatora ... kiedy uzna to za konieczne. Do tego momentu tak niszą pamięć rodzimą, że może spowodować, że Linux oom_killer zabije proces bezkarnie. Głównym problemem jest to, że finalizacja w Javie jest bardzo niewiarygodna, a G1 pogorszyło ją do 7.0.2. Morał z historii: uwolnij zasoby natywne tak szybko, jak to tylko możliwe; finalizator jest po prostu zbyt biedny.

  • Ten sam przypadek z java.util.zip.Deflater. Ta jest o wiele gorsza, ponieważ Deflater jest głodny w Javie, tzn. Zawsze używa 15 bitów (max) i 8 poziomów pamięci (9 to max), przydzielając kilkaset KB pamięci rodzimej. Na szczęście, Deflater nie jest szeroko stosowany i według mojej wiedzy JDK nie zawiera żadnych nadużyć. Zawsze dzwoń end() jeśli ręcznie utworzysz Deflater lub Inflater. Najlepsza część z dwóch ostatnich: nie możesz ich znaleźć za pomocą standardowych narzędzi do profilowania.

(Mogę dodać więcej czasu, jaki napotkałem na żądanie).

Powodzenia i bądź bezpieczny; przecieki są złe!


228



Creating but not starting a Thread... Yikes, zostałem poważnie ugryziony przez to kilka wieków temu! (Java 1.3) - leonbloy
@leonbloy, zanim było jeszcze gorzej, ponieważ wątek został dodany bezpośrednio do grupy wątków, nie rozpoczęcie oznaczało bardzo twardego przecieku. Nie tylko zwiększa unstarted liczyć, ale to zapobiega niszczeniu grupy wątków (mniejsze zło, ale nadal przeciek) - bestsss
Dziękuję Ci! "Powołanie ThreadGroup.destroy() gdy ThreadGroup sam nie ma nici ... " jest niesamowicie subtelnym błędem; Ścigam to od wielu godzin, błądząc, ponieważ wyliczenie nici w GUI kontrolnym nic nie pokazało, ale grupa wątków i, prawdopodobnie, przynajmniej jedna grupa dzieci nie zniknie. - Lawrence Dol
@ Bestsss: Jestem ciekawy, dlaczego chcesz usunąć hak zamykający, biorąc pod uwagę, że działa on przy zamykaniu JVM? - Lawrence Dol


Większość przykładów tutaj jest "zbyt skomplikowana". Są to skrajne przypadki. Na tych przykładach programista popełnił błąd (jak na przykład redefinicja równości / hashcode) lub został ukąszony przez narożną skrzynkę JVM / JAVA (obciążenie klasy statycznymi ...). Wydaje mi się, że nie jest to typowy przykład poszukiwanego przez ankietera lub nawet najczęstszego przypadku.

Ale są naprawdę prostsze przypadki wycieków pamięci. Śmieciarz tylko uwalnia to, czego już się nie odwołuje. My, jako programiści Javy, nie dbamy o pamięć. Przydzielamy go w razie potrzeby i automatycznie zwalniamy. W porządku.

Jednak każda długowieczna aplikacja ma tendencję do dzielenia się państwem. Może to być wszystko, statyka, singletony ... Często nietrywialne aplikacje mają tendencję do tworzenia złożonych wykresów obiektów. Po prostu zapominając ustawić odwołanie do wartości null lub częściej zapominając o usunięciu jednego obiektu z kolekcji wystarczy, aby spowodować wyciek pamięci.

Oczywiście wszelkiego rodzaju słuchacze (np. Słuchacze UI), pamięci podręczne lub dowolne długo używane współdzielenie wydają się powodować wycieki pamięci, jeśli nie są właściwie obsługiwane. Należy rozumieć, że nie jest to narożnik Java lub problem z śmieciarzem. To jest problem projektowy. Projektujemy, aby dodać słuchacza do obiektu długowiecznego, ale nie usuwamy słuchacza, gdy nie jest on już potrzebny. Cache'ujemy obiekty, ale nie mamy strategii usuwania ich z pamięci podręcznej.

Być może mamy złożony wykres, który przechowuje poprzedni stan, który jest wymagany przez obliczenia. Ale poprzedni stan sam w sobie jest związany z państwem wcześniej i tak dalej.

Tak jak musimy zamknąć połączenia lub pliki SQL. Musimy ustawić odpowiednie odwołania do wartości NULL i usunąć elementy z kolekcji. Będziemy mieć odpowiednie strategie buforowania (maksymalny rozmiar pamięci, liczba elementów lub timerów). Wszystkie obiekty, które umożliwiają powiadamianie nasłuchującego, muszą dostarczyć zarówno metodę addListener, jak i removeListener. A kiedy te powiadomienia nie są już używane, muszą wyczyścić swoją listę odbiorców.

Przeciek pamięci jest naprawdę możliwy i jest doskonale przewidywalny. Nie ma potrzeby używania specjalnych funkcji językowych ani narożnych. Wycieki pamięci są albo wskaźnikiem, że czegoś może brakować, albo nawet problemów z projektowaniem.


150



Uważam, że to zabawne, że w przypadku innych odpowiedzi ludzie szukają tych skrajnych przypadków i sztuczek i wydaje się, że kompletnie ich nie rozumieją. Mogliby po prostu pokazać kod, który zachowałby bezużyteczne odniesienia do obiektów, których nigdy więcej nie użyje i nigdy nie usunie tych odniesień; Można powiedzieć, że te przypadki nie są "prawdziwymi" wyciekami pamięci, ponieważ wciąż istnieją odniesienia do tych obiektów, ale jeśli program nigdy nie użyje tych odniesień ponownie, a także nigdy ich nie upuści, jest całkowicie równoważny (i tak zły, jak) " prawdziwy wyciek pamięci ". - ehabkost
@Nicolas Bousquet: "Przeciek pamięci jest naprawdę całkowicie możliwy"   Dziękuję bardzo. +15 przegranych. Miły. Zostałem tu krzyknięty za stwierdzenie tego faktu, jako przesłanki pytania o języku Go: stackoverflow.com/questions/4400311   To pytanie wciąż ma negatywne oceny :( - SyntaxT3rr0r
GC w Javie i .NET jest w pewnym sensie przewidziany na wykresie założenia obiektów, które zawierają odniesienia do innych obiektów, jest taki sam jak wykres obiektów, które "interesują się" innymi obiektami. W rzeczywistości jest możliwe, że krawędzie mogą istnieć na wykresie odniesienia, który nie reprezentuje relacji "opiekuńczych", a obiekt może dbać o istnienie innego obiektu, nawet jeśli nie ma bezpośredniej lub pośredniej ścieżki odniesienia (nawet przy użyciu WeakReference) istnieje od jednego do drugiego. Jeśli odwołanie do obiektu ma zapasowy bit, pomocne może być posiadanie wskaźnika "dba o cel" ... - supercat
... i poproś system o powiadomienia (za pośrednictwem środków podobnych do PhantomReference) jeśli znaleziono obiekt, który nie ma nikogo, kto by się nim przejmował. WeakReference przychodzi nieco blisko, ale musi zostać przekonwertowany na silne odwołanie, zanim będzie można go użyć; jeśli cykl GC występuje, gdy silne odniesienie istnieje, cel zostanie uznany za przydatny. - supercat
Jest to moim zdaniem poprawna odpowiedź. Napisaliśmy symulację wiele lat temu. W jakiś sposób przypadkowo połączono poprzedni stan z bieżącym stanem, powodując wyciek pamięci. Ze względu na ostateczny termin nigdy nie rozwiązaliśmy problemu przecieku pamięci, ale zrobiliśmy z tego "cechę", dokumentując go. - nalply


Odpowiedź zależy całkowicie od tego, o czym pytał ankieter.

Czy w praktyce możliwe jest wyciekanie Javy? Oczywiście, że jest, i jest wiele przykładów w innych odpowiedziach.

Ale jest wiele pytań meta, które mogły być zadawane?

  • Czy teoretycznie "doskonała" implementacja Java jest podatna na przecieki?
  • Czy kandydat rozumie różnicę między teorią a rzeczywistością?
  • Czy kandydat rozumie, jak działa zbieranie śmieci?
  • Lub w jaki sposób garbage collection ma działać w idealnym przypadku?
  • Czy wiedzą, że mogą wywoływać inne języki przez natywne interfejsy?
  • Czy wiedzą, że przeciekają pamięć w tych innych językach?
  • Czy kandydat wie nawet, czym jest zarządzanie pamięcią i co dzieje się za kulisami w Javie?

Czytam twoje meta-pytanie jako "Jaką odpowiedź mogłem wykorzystać w tej sytuacji wywiadu". I dlatego zamierzam skupić się na umiejętnościach przeprowadzania wywiadów zamiast na Javie. Sądzę, że bardziej prawdopodobne jest powtórzenie sytuacji, w której nie znasz odpowiedzi na pytanie w wywiadzie, a nie będziesz musiał wiedzieć, jak doprowadzić do wycieku Java. Miejmy nadzieję, że to pomoże.

Jedną z najważniejszych umiejętności, które możesz rozwinąć podczas rozmowy kwalifikacyjnej, jest uczenie się aktywnego słuchania pytań i pracy z ankieterem w celu wydobycia ich intencji. Pozwala to nie tylko odpowiedzieć na pytanie w sposób, w jaki chcą, ale także pokazuje, że posiadasz istotne umiejętności komunikacyjne. A jeśli chodzi o wybór między wieloma równie utalentowanymi programistami, zatrudnię osobę, która słucha, myśli i rozumie, zanim zareaguje za każdym razem.


133



Za każdym razem, gdy zadałem to pytanie, szukam całkiem prostej odpowiedzi - ciągle rosnącą kolejkę, nie ma na końcu bliskiego db itp., A nie dziwne informacje o klasach / wątkach klasowych, oznacza, że ​​rozumieją, co gc może i nie może zrobić dla ciebie. Zależy, jak sądzę, od pracy, z którą rozmawiasz. - DaveC
Proszę spojrzeć na moje pytanie, dzięki stackoverflow.com/questions/31108772/... - Daniel Newtown


Oto przykład bezsensowny, jeśli nie rozumiesz JDBC. Lub przynajmniej, jak JDBC spodziewa się, że programista zostanie zamknięty Connection, Statement i ResultSet instancje przed ich odrzuceniem lub utratą odniesień do nich, zamiast polegać na implementacji finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Problem z powyższym jest taki, że Connection Obiekt nie jest zamknięty, a więc fizyczne połączenie pozostanie otwarte, dopóki śmieciarz nie pojawi się i nie będzie dostępny. GC odwoła się do finalize metoda, ale istnieją sterowniki JDBC, które nie implementują finalizeprzynajmniej nie w taki sam sposób Connection.close jest zaimplementowane. Wynikające z tego zachowanie polega na tym, że pamięć zostanie odzyskana z powodu gromadzenia nieosiągalnych obiektów, zasobów (w tym pamięci) powiązanych z Connection obiekt może po prostu nie zostać odzyskany.

W takim przypadku, gdy Connection„s finalize metoda nie oczyszcza wszystkiego, można faktycznie stwierdzić, że fizyczne połączenie z serwerem bazy danych będzie trwać kilka cykli zbierania śmieci, dopóki serwer bazy danych ostatecznie nie zorientuje się, że połączenie nie jest aktywne (jeśli tak) i powinno zostać zamknięte.

Nawet jeśli sterownik JDBC miałby zostać wdrożony finalize, możliwe jest wyrzucanie wyjątków podczas finalizacji. Wynikające stąd zachowanie polega na tym, że jakakolwiek pamięć skojarzona z "uśpionym" obiektem nie zostanie odzyskana, ponieważ finalize gwarantuje się, że zostanie wywołany tylko raz.

Powyższy scenariusz napotkania wyjątków podczas finalizacji obiektu wiąże się z innym scenariuszem, który może prowadzić do wycieku pamięci - wskrzeszenia obiektu. Wskrzeszenie obiektu jest często wykonywane celowo, tworząc silne odniesienie do obiektu od sfinalizowania, z innego obiektu. Gdy nieprawidłowe wskrzeszenie obiektu doprowadzi do wycieku pamięci w połączeniu z innymi źródłami wycieków pamięci.

Istnieje wiele innych przykładów, które można wyczarować

  • Zarządzanie a List na przykład, gdy dodajesz tylko do listy i nie usuwasz jej (chociaż powinieneś pozbywać się elementów, których już nie potrzebujesz), lub
  • Otwarcie Sockets lub Files, ale nie zamykając ich, gdy nie są już potrzebne (podobne do powyższego przykładu z udziałem Connection klasa).
  • Nie rozładowywanie Singletonów podczas likwidacji aplikacji Java EE. Wygląda na to, że Classloader, który załadował klasę singleton, zachowa odniesienie do klasy, a zatem instancja singleton nigdy nie zostanie zebrana. Kiedy wdrażana jest nowa instancja aplikacji, zwykle tworzony jest nowy moduł ładujący klasy, a poprzedni moduł ładujący klasy będzie nadal istnieć ze względu na singleton.

113



Osiągniesz maksymalny limit otwartego połączenia, zanim osiągniesz limit pamięci zazwyczaj. Nie pytaj mnie, dlaczego wiem ... - Hardwareguy
Sterownik Oracle JDBC znany jest z tego. - chotchki
@Hardwareguy Znacznie przekroczyłem granice połączeń baz danych SQL, dopóki nie wstawię Connection.close do końcowego bloku wszystkich moich wywołań SQL. Dla dodatkowej zabawy zadzwoniłem do kilku długotrwałych procedur przechowywanych Oracle, które wymagały blokad po stronie Java, aby zapobiec zbyt wielu połączeniom z bazą danych. - Michael Shopsin
@Hardwareguy Jest to interesujące, ale nie jest konieczne, aby rzeczywiste limity połączeń trafiały we wszystkie środowiska. Na przykład dla aplikacji wdrożonej na serwerze aplikacji weblogic 11g zauważyłem wycieki połączeń na dużą skalę. Jednak ze względu na opcję zbierania wycieków połączenia bazy danych pozostały dostępne, podczas gdy wprowadzano wycieki pamięci. Nie jestem pewien wszystkich środowisk. - Aseem Bansal
Z mojego doświadczenia wynika, że ​​masz wyciek pamięci, nawet jeśli zamkniesz połączenie. Najpierw należy zamknąć ResultSet i PreparedStatement. Miałem serwer, który wielokrotnie padał z powodu OutOfMemoryErrors po godzinach, a nawet dniach pracy, dopóki nie zacząłem tego robić. - Bjørn Stenfeldt


Prawdopodobnie jednym z najprostszych przykładów potencjalnego wycieku pamięci i jak go uniknąć jest implementacja ArrayList.remove (int):

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Jeśli implementowałeś to sam, czy myślałeś, aby wyczyścić element tablicy, który nie jest już używany (elementData[--size] = null)? To odniesienie może utrzymać przy życiu ogromny obiekt ...


102



'elementData [- size] = null;' czy polana, myślę ... - maniek
A gdzie jest tu wyciek pamięci? - rds
@maniek: Nie chciałem sugerować, że ten kod wykazuje wyciek pamięci. Przytoczyłem to, aby pokazać, że czasami nieoczywisty kod jest wymagany, aby uniknąć przypadkowego zatrzymania obiektu. - meriton
Co to jest RangeCheck (indeks); ? - Koray Tugay
Joshua Bloch podał ten przykład w Effective Java, pokazując prostą implementację Stacków. Bardzo dobra odpowiedź. - rents