2011-02-09 12 views
26

Powiel możliwe:
Creating a memory leak with JavaNajłatwiejszy sposób na spowodowanie wycieku pamięci w Javie?

Jaki jest najprostszy sposób, aby spowodować wyciek pamięci java?

+0

Szukasz wymyślonego przykładu lub bardzo powszechnego błędu w programowaniu? – mikerobi

+0

przykładowy przykład byłby najlepszy proszę. –

+0

Przeciek pamięci jest tworzony, gdy obiekt, który nie jest przeznaczony do użycia, ma do niego odniesienie. Prawie każdy program, który można napisać, byłby wymyślnym przykładem wycieku pamięci. – OrangeDog

Odpowiedz

27

Nie można naprawdę „wyciek pamięci” w Javie, chyba że:

  • intern ciągi
  • wygenerować klas pamięci
  • Przeciek w natywnego kodu zwanego przez JNI
  • utrzymać odniesienia do rzeczy, które nie chcę w jakimś zapomnianym lub niejasnym miejscu.

Uważam, że jesteś zainteresowany ostatnim przypadkiem. Wspólne scenariusze:

  • słuchaczy, zwłaszcza wykonane z wewnętrznymi klas
  • buforuje.

Dobrym przykładem byłoby:

  • zbudować gui Swing, który uruchamia potencjalnie nieograniczoną liczbę modalnych okien;
  • mają modalne okno zrobić coś takiego podczas inicjalizacji:
     
    StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){ 
        public void actionPerformed(ActionEvent e){ 
        // do nothing... 
        } 
    }) 
    

Zarejestrowany akcja nic nie robi, ale spowoduje to modalne okno do zrelaksowania się w pamięci na zawsze, nawet po zamknięciu, powodując wyciek - ponieważ słuchacze nigdy nie są niezarejestrowani, a każdy anonimowy przedmiot klasy wewnętrznej posiada odniesienie (niewidoczne) do zewnętrznego obiektu. Co więcej - każdy obiekt przywoływany z okien modalnych również ma szansę na wyciek.

Z tego powodu biblioteki takie jak EventBus domyślnie używają słabych referencji.

Oprócz słuchaczy, innymi typowymi przykładami są pamięci podręczne, ale nie mogę wymyślić dobrego przykładu.

+4

Internowane łańcuchy nie są tak naprawdę wyciekami pamięci, mogą też zostać pobrane ze śmieci. Problem polega tylko na tym, że (w typowych implementacjach) znajdują się w specjalnym obszarze pamięci (PermGen), który jest mniejszy niż reszta pamięci, a zatem łatwiej wypełnia się. –

+0

Masz rację. Interned Strings nie są "prawdziwymi" przeciekami (wtedy znowu "rzeczywiste wycieki" nie są możliwe w przypadku jvm). Niemniej jednak, perm zostaje zebrany tylko wtedy, gdy wszystko inne zawiedzie, a jego zawartość przetrwa poważne kolekcje, więc jest to jedno z niewielu źródeł prawdziwych problemów z pamięcią. Również internowane łańcuchy zajmują miejsce, nawet jeśli nie są przywoływane z twojego programu. W tym sensie są one tak blisko wycieku, jak to tylko możliwe. – fdreger

+0

Jedna z najlepszych odpowiedzi: https://www.reddit.com/r/AMA/comments/7z8tw0/people_who_are_experts_in_their_fieldhobby_ama/dumd8y8/ –

3
  1. Tworzenie kolekcji obiektów w zakresie klasy
  2. Okresowo dodawać nowe obiekty do kolekcji
  3. Nie upuszczać odwołanie do instancji klasy, która posiada kolekcję

Ponieważ istnieje jest zawsze odniesieniem do kolekcji i instancji obiektu, który jest właścicielem kolekcji, której Garbage Collector nigdy nie wyczyści tej pamięci, powodując w ten sposób "wyciek" w czasie.

8
public static List<byte[]> list = new ArrayList<byte[]>(); 

A następnie dodaj (duże) tablice bez ich usuwania. W pewnym momencie zabraknie ci pamięci bez podejrzeń. (Możesz to zrobić z dowolnymi obiektami, ale z dużymi, pełnymi tablicami możesz szybciej wyczerpać pamięć)

W Javie, jeśli usuniesz obiekt (w jego zasięgu), zostanie on zbuforowany. Więc musisz mieć odniesienie do niego, aby mieć problem z pamięcią.

+0

Spowoduje to, że skończy Ci się pamięć, ale jak możesz mieć wyciek, jeśli nigdy nie zrobisz nic, aby złamać odniesienie do obiektu? – mikerobi

+5

@mikerobi - wyciek pamięci występuje, gdy "zajmiesz" trochę pamięci bez jej czyszczenia (i bez jej używania). Jeśli jednak usuniesz obiekt, zostaną zebrane śmieci. – Bozho

+1

Rozumiem to, ale nie uważam, że jest to przeciek w każdym przypadku. Jest to z pewnością przeciek, jeśli przez pomyłkę wykonasz statyczną zmienną klasy, może to być przeciek, jeśli używasz go jako wartości globalnej w długim procesie. Nie jest to przeciek, jeśli masz zamiar utrzymywać dane do czasu zakończenia programu. Fakt, że nieskończona pętla wyczerpuje twoją pamięć nie ma nic wspólnego z faktem, że jest to przeciek. Wiele nieszczelności nie zostaje zauważonych, chyba że nieustannie przydzielają nowe dane, ale posiadanie ustalonego fragmentu osieroconej pamięci wciąż jest przeciekiem. – mikerobi

8

Oto prosty przykład

public class finalizer { 
    @Override 
     protected void finalize() throws Throwable { 
     while (true) { 
      Thread.yield(); 
     } 
    } 

    public static void main(String[] args) { 
     while (true) { 
      for (int i = 0; i < 100000; i++) { 
       finalizer f = new finalizer(); 
      } 

      System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!"); 
     } 
    } 
} 
+0

Czy mógłbyś wyjaśnić trochę, w jaki sposób osiągasz wyciek pamięci w tym przykładzie? – TheBlueNotebook

+0

Nie jestem pewien, ale ten kod wydaje się działać, przynajmniej zabił mój komputer i procesy zachowały się w tle nawet po zamknięciu eclipse – pescamillam

+1

@TheBlueNotebook Sfinalizowana metoda, którą przejął, jest tym, co zwykle wywołuje Java, gdy ma zwolnić pamięć dla obiekt. W swojej głównej metodzie tworzy 100K finalizatorów, a następnie mówi maszynie JVM, by uwolnić całą pamięć. JVM robi to grzecznie i wywołuje sfinalizowanie przed uwolnieniem pamięci. Sfinalizowana metoda wywołuje na zawsze zbiory, więc obiekty nigdy nie są usuwane, ale główna pętla jest kontynuowana, tworząc kolejne 100K Obiekty, które nigdy nie zostaną usunięte, potem inne, potem kolejne ... – Jeutnarg

3

Z tego co czytałem w większości głosowali odpowiedź, jesteś najprawdopodobniej prosząc o wycieku pamięci C-podobne. Cóż, skoro jest kolekcja garbagge, nie można przydzielić obiektu, stracić wszystkich jego odniesień i nadal zajmować pamięć - to byłby poważny bug JVM.

Z drugiej strony, może się zdarzyć, że wycieknie nici - co oczywiście spowoduje ten stan, ponieważ będziesz mieć wątek uruchomiony z jego odniesieniami do obiektów i możesz utracić odniesienie do wątku. Nadal można uzyskać odniesienie nić przez API - http://www.exampledepot.com/egs/java.lang/ListThreads.html

0

Spróbuj prostą klasę:

public class Memory { 
private Map<String, List<Object>> dontGarbageMe = new HashMap<String, List<Object>>(); 

public Memory() { 
    dontGarbageMe.put("map", new ArrayList<Object>()); 
} 

public void useMemInMB(long size) { 
    System.out.println("Before=" + getFreeMemInMB() + " MB"); 

    long before = getFreeMemInMB(); 
    while ((before - getFreeMemInMB()) < size) { 
     dontGarbageMe.get("map").add("aaaaaaaaaaaaaaaaaaaaaa"); 
    } 

    dontGarbageMe.put("map", null); 

    System.out.println("After=" + getFreeMemInMB() + " MB"); 
} 

private long getFreeMemInMB() { 
    return Runtime.getRuntime().freeMemory()/(1024 * 1024); 
} 

public static void main(String[] args) { 
    Memory m = new Memory(); 
    m.useMemInMB(15); // put here apropriate huge value 
} 
} 
+0

To jest najbardziej skomplikowany prosty przykład tutaj. ;) –

+0

Gdzie jest wyciek? nie jest lista zwolniona po GC? –

15

„przeciek pamięci, w informatyce (lub wycieku, w tym kontekście), występuje wtedy, gdy program komputerowy zużywa pamięć, ale nie może jej zwolnić z powrotem do systemu operacyjnego. " (Wikipedia)

Łatwa odpowiedź brzmi: Nie możesz. Java automatycznie zarządza pamięcią i uwolni zasoby, które nie są dla ciebie potrzebne. Nie możesz tego powstrzymać. ZAWSZE będzie w stanie zwolnić zasoby. W programach z ręcznym zarządzaniem pamięcią jest inaczej. Nie możesz dostać pamięci w C używając malloc(). Aby zwolnić pamięć, potrzebujesz wskaźnika, który zwrócił malloc i wywołaj free() na nim. Ale jeśli nie masz już wskaźnika (nadpisany lub przekroczony czas życia), to niestety nie jesteś w stanie uwolnić tej pamięci, a tym samym masz wyciek pamięci.

Wszystkie pozostałe odpowiedzi są w mojej definicji naprawdę nieszczelności pamięci. Wszystkie one mają na celu szybkie wypełnienie pamięci bezsensownymi rzeczami. Ale w każdej chwili możesz nadal usuwać zaznaczenie obiektów, które stworzyłeś, a tym samym uwolnić pamięć -> NIE PRZERWAĆ.Odpowiedź akonradu jest dość bliska, ponieważ muszę przyznać, że jego rozwiązaniem jest po prostu "rozbić" śmieciarza, zmuszając go do nieskończonej pętli).

Długa odpowiedź brzmi: Możesz dostać wyciek pamięci, pisząc bibliotekę dla Javy, używając JNI, który może mieć ręczne zarządzanie pamięcią i tym samym mieć przecieki pamięci. Jeśli wywołasz tę bibliotekę, proces java spowoduje wyciek pamięci. Lub możesz mieć błędy w JVM, więc JVM traci pamięć. Są prawdopodobnie błędy w JVM, mogą być nawet niektóre znane, ponieważ odśmiecanie nie jest takie proste, ale to wciąż jest błąd. Z założenia nie jest to możliwe. Być może pytasz o kod Java, który jest spowodowany przez taki błąd. Przepraszam, że nie znam jednego i może to już nie być błąd w następnej wersji Java.

+1

"Ale w każdej chwili możesz nadal usuwać zaznaczenie obiektów, które stworzyłeś, a tym samym uwolnić pamięć". Nie zgadzam się. Implementor klasy może ukrywać uchwyty obiektów przed światem zewnętrznym. –

+0

@trinithis: Jeśli masz obiekt, który prywatnie marnuje pamięć przez przydzielanie ogromnej ilości pamięci, nie możesz wymusić na obiekcie zwolnienia pamięci bez odrzucania tego obiektu. Ale w tym przypadku wciąż marnuje pamięć, a nie przeciek. Pamięć może zostać zwolniona. Zostanie on zwolniony, gdy obiekt odwołujący się do zmarnowanej pamięci nie będzie już przywoływany. A może źle cię zrozumiałem? – yankee

+0

Myślę, że źle zrozumiałem, co rozumiesz przez "dereferencję". Myślałem o znaczeniu słowa w języku C. –

0

Wygląda na to, że większość odpowiedzi nie dotyczy przecieków pamięci w stylu C.

Pomyślałem, że dodam przykład klasy biblioteki z błędem, który da ci wyjątek z pamięci. Ponownie, nie jest to prawdziwy wyciek pamięci, ale jest przykładem czegoś, czego brakuje pamięci, czego byś się nie spodziewał.

public class Scratch { 
    public static void main(String[] args) throws Exception { 
     long lastOut = System.currentTimeMillis(); 
     File file = new File("deleteme.txt"); 

     ObjectOutputStream out; 
     try { 
      out = new ObjectOutputStream(
        new FileOutputStream("deleteme.txt")); 

      while (true) { 
       out.writeUnshared(new LittleObject()); 
       if ((System.currentTimeMillis() - lastOut) > 2000) { 
        lastOut = System.currentTimeMillis(); 
        System.out.println("Size " + file.length()); 
        // out.reset(); 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

class LittleObject implements Serializable { 
    int x = 0; 
} 

Znajdziesz oryginalny kod i opis błędu na

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

0

Poniższy niezwykle wymyślony Box klasy będzie przeciekać pamięci, jeżeli są stosowane. Obiekty, które są put w tej klasie, są ostatecznie (po kolejnym wywołaniu do put, aby być precyzyjnym ... pod warunkiem, że ten sam obiekt nie jest do niego ponownie dostępny). Niedostępny dla świata zewnętrznego. Nie można ich usunąć przez tę klasę, ale ta klasa gwarantuje, że nie można ich zebrać. To jest PRAWDZIWY wyciek. Wiem, że to naprawdę wymyślone, ale podobne przypadki można zrobić przypadkowo.

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Stack; 

public class Box <E> { 
    private final Collection<Box<?>> createdBoxes = new ArrayList<Box<?>>(); 
    private final Stack<E> stack = new Stack<E>(); 

    public Box() { 
     createdBoxes.add(this); 
    } 

    public void put (E e) { 
     stack.push(e); 
    } 

    public E get() { 
     if (stack.isEmpty()) { 
      return null; 
     } 
     return stack.peek(); 
    } 
} 
Powiązane problemy