it-swarm.com.de

Erstellen eines Speicherverlusts mit Java

Ich hatte gerade ein Interview und wurde gebeten, ein Speicherverlust mit Java zu erstellen.
Unnötig zu erwähnen, dass ich mich ziemlich dumm fühlte, keine Ahnung zu haben, wie ich überhaupt anfangen sollte, eine zu erstellen.

Was wäre ein Beispiel?

3018
Mat B.

Hier ist eine gute Möglichkeit, einen echten Speicherverlust (Objekte, auf die durch Ausführen von Code nicht zugegriffen werden kann, die jedoch noch im Speicher gespeichert sind) in reinem Java zu erstellen:

  1. Die Anwendung erstellt einen Thread mit langer Laufzeit (oder verwendet einen Thread-Pool, um noch schneller Daten zu verlieren).
  2. Der Thread lädt eine Klasse über einen (optionalen) ClassLoader.
  3. Die Klasse reserviert einen großen Teil des Speichers (z. B. new byte[1000000]), speichert einen starken Verweis darauf in einem statischen Feld und speichert dann einen Verweis auf sich selbst in einem ThreadLocal. Das Zuweisen des zusätzlichen Arbeitsspeichers ist optional (es reicht aus, die Klasseninstanz zu löschen), aber das Leck wird dadurch viel schneller ausgeführt.
  4. Der Thread löscht alle Verweise auf die benutzerdefinierte Klasse oder den ClassLoader, aus dem er geladen wurde.
  5. Wiederholen.

Dies funktioniert, weil ThreadLocal einen Verweis auf das Objekt behält, der einen Verweis auf seine Klasse behält, der wiederum einen Verweis auf seinen ClassLoader behält. Der ClassLoader speichert wiederum einen Verweis auf alle Klassen, die er geladen hat.

(In vielen JVM-Implementierungen war dies noch schlimmer, insbesondere vor Java 7, da Klassen und ClassLoader direkt in permgen zugewiesen und nie gecodet wurden. Unabhängig davon, wie die JVM das Entladen von Klassen handhabt, Ein ThreadLocal verhindert weiterhin, dass ein Class-Objekt zurückgefordert wird.)

Eine Variation dieses Musters ist, warum Anwendungscontainer (wie Tomcat) Speicher wie ein Sieb verlieren können, wenn Sie häufig Anwendungen erneut bereitstellen, die ThreadLocals auf irgendeine Weise verwenden. (Da der Anwendungscontainer Threads wie beschrieben verwendet und jedes Mal, wenn Sie die Anwendung erneut bereitstellen, wird ein neuer ClassLoader verwendet.)

Update : Da immer wieder Leute danach fragen, hier ist ein Beispielcode, der dieses Verhalten in Aktion zeigt .

2186
Daniel Pryden

Statisches Feld mit Objektreferenz [esp final field]

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

Aufruf von String.intern() für einen längeren String

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();

(Nicht geschlossen) offene Streams (Datei, Netzwerk usw.)

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

Nicht geschlossene Verbindungen

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

Bereiche, die für den Garbage Collector von JVM nicht erreichbar sind , z. B. durch systemeigene Methoden zugewiesener Speicher

In Webanwendungen werden einige Objekte im Anwendungsbereich gespeichert, bis die Anwendung explizit gestoppt oder entfernt wird.

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

Falsche oder unangemessene JVM-Optionen , z. B. die Option noclassgc in IBM JDK, die die Erfassung nicht verwendeter Klassenbereinigungen verhindert

Siehe IBM JDK-Einstellungen .

1167
Prashant Bhate

Eine einfache Sache, die Sie tun können, ist, ein HashSet mit einem falschen (oder nicht existierenden) hashCode() oder equals() zu verwenden und dann weiterhin "Duplikate" hinzuzufügen. Anstatt Duplikate wie gewünscht zu ignorieren, wird die Menge immer größer und Sie können sie nicht entfernen.

Wenn Sie möchten, dass diese fehlerhaften Schlüssel/Elemente herumhängen, können Sie ein statisches Feld wie verwenden

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.
441
Peter Lawrey

Es wird einen nicht offensichtlichen Fall geben, in dem Java undicht ist, abgesehen vom Standardfall vergessener Listener, statischer Verweise, gefälschter/modifizierbarer Schlüssel in Hashmaps oder einfach festgefahrener Threads, ohne dass die Chance besteht, ihren Lebenszyklus zu beenden .

  • File.deleteOnExit() - verliert immer die Zeichenkette, wenn es sich bei der Zeichenfolge um eine Teilzeichenfolge handelt, ist das Leck noch schlimmer (das zugrunde liegende Zeichen [] ist ebenfalls durchgesickert). - in Java 7 kopiert der Teilstring auch den char[], so dass der letztere nicht zutrifft; @ Daniel, keine Notwendigkeit für Stimmen, aber.

Ich werde mich auf Threads konzentrieren, um die Gefahr von nicht verwalteten Threads zu verdeutlichen. Ich möchte nicht einmal Swing berühren.

  • Runtime.addShutdownHook und nicht entfernen ... und dann auch mit removeShutdownHook aufgrund eines Fehlers in der ThreadGroup-Klasse in Bezug auf nicht gestartete Threads möglicherweise nicht gesammelt, effektiv die ThreadGroup verlieren. JGroup hat das Leck in GossipRouter.

  • Das Erstellen, aber nicht das Starten eines Thread erfolgt in derselben Kategorie wie oben.

  • Das Erstellen eines Threads erbt die ContextClassLoader und AccessControlContext sowie die ThreadGroup und alle InheritedThreadLocal. Alle diese Verweise sind potenzielle Lecks, zusammen mit den gesamten vom Klassenladeprogramm geladenen Klassen und allen statische Verweise und ja-ja. Der Effekt ist besonders sichtbar mit dem gesamten j.u.c.Executor-Framework, das eine supereinfache ThreadFactory -Oberfläche bietet, aber die meisten Entwickler haben keine Ahnung von der lauernden Gefahr. Auch viele Bibliotheken starten Threads auf Anfrage (viel zu viele branchenübliche Bibliotheken).

  • ThreadLocal Caches; das sind in vielen Fällen böse. Ich bin mir sicher, dass jeder eine Menge einfacher Caches gesehen hat, die auf ThreadLocal basieren. Nun, die schlechte Nachricht: Wenn der Thread im Kontext ClassLoader länger als erwartet läuft, ist es ein reines Nice little leak. Verwenden Sie keine ThreadLocal-Caches, es sei denn, dies wird wirklich benötigt.

  • Aufruf von ThreadGroup.destroy(), wenn die ThreadGroup selbst keine Threads hat, aber immer noch untergeordnete ThreadGroups enthält. Ein fehlerhaftes Leck, das verhindert, dass die ThreadGroup von ihrem übergeordneten Element entfernt wird, aber alle untergeordneten Elemente sind nicht mehr aufzählbar.

  • Bei Verwendung von WeakHashMap und dem Wert (in) wird direkt auf den Schlüssel verwiesen. Dies ist schwer zu finden, ohne einen Heap-Dump. Dies gilt für alle erweiterten Weak/SoftReference, die möglicherweise einen harten Verweis auf das geschützte Objekt enthalten.

  • Verwenden von Java.net.URL mit dem HTTP (S) -Protokoll und Laden der Ressource von (!). Dies ist eine Besonderheit, die KeepAliveCache erstellt einen neuen Thread in der System-ThreadGroup, der den Kontext-Classloader des aktuellen Threads verliert. Der Thread wird bei der ersten Anforderung erstellt, wenn kein aktiver Thread vorhanden ist, sodass Sie möglicherweise Glück haben oder einfach nur lecken. Das Leck ist in Java 7 bereits behoben, und der Code, der den Thread ordnungsgemäß erstellt, entfernt den Kontextklassenladeprogramm. Es gibt nur noch wenige Fälle (wie ImageFetcher, auch behoben) um ähnliche Threads zu erstellen.

  • Verwenden von InflaterInputStream Übergeben von new Java.util.Zip.Inflater() im Konstruktor (z. B. PNGImageDecoder) und Aufrufen von end() des Inflaters. Wenn Sie den Konstruktor nur mit new übergeben, hat dies keine Chance ... Und ja, wenn Sie close() für den Stream aufrufen, wird der Inflater nicht geschlossen, wenn er manuell als Konstruktorparameter übergeben wird. Dies ist kein echtes Leck, da es vom Finalizer freigegeben wird ... wenn es für notwendig erachtet wird. Bis zu diesem Zeitpunkt wird der native Speicher so stark beansprucht, dass Linux oom_killer den Prozess ungestraft beenden kann. Das Hauptproblem ist, dass die Finalisierung in Java sehr unzuverlässig ist und G1 es bis 7.0.2 noch schlimmer gemacht hat. Moral der Geschichte: Geben Sie einheimische Ressourcen frei, sobald Sie können; Der Finalizer ist einfach zu arm.

  • Der gleiche Fall mit Java.util.Zip.Deflater. Dies ist weitaus schlimmer, da Deflater in Java speicherhungrig ist, d. H. Immer 15 Bit (maximal) und 8 Speicherebenen (maximal 9) verwendet, die mehrere Hundert KB nativen Speicher zuweisen. Glücklicherweise ist Deflater nicht weit verbreitet und meines Wissens enthält JDK keine Missbräuche. Rufen Sie immer end() auf, wenn Sie manuell Deflater oder Inflater erstellen. Der beste Teil der letzten beiden: Sie können sie nicht über die normalen verfügbaren Profilerstellungstools finden.

(Ich kann auf Anfrage weitere Zeitverschwender hinzufügen.)

Viel Glück und bleib in Sicherheit; Leckagen sind böse!

262
bestsss

Die meisten Beispiele sind hier "zu komplex". Sie sind Randfälle. Mit diesen Beispielen ist dem Programmierer ein Fehler unterlaufen (wie beispielsweise, dass er Equals/Hashcode nicht neu definiert), oder er wurde von einem Eckfall der JVM/Java gebissen (Laden der Klasse mit Static ...). Ich denke, das ist nicht die Art von Beispiel, die ein Interviewer haben möchte oder auch nur der häufigste Fall.

Aber es gibt wirklich einfachere Fälle für Speicherverluste. Der Garbage Collector gibt nur das frei, was nicht mehr referenziert wird. Wir als Java Entwickler kümmern uns nicht um Speicher. Wir ordnen es bei Bedarf zu und lassen es automatisch freigeben. Fein.

Aber jede langlebige Anwendung hat in der Regel einen gemeinsamen Status. Es kann alles sein, Statik, Singletons ... Oft neigen nicht-triviale Anwendungen dazu, komplexe Objekte grafisch darzustellen. Nur zu vergessen, einen Verweis auf null zu setzen, oder öfter zu vergessen, ein Objekt aus einer Sammlung zu entfernen, reicht aus, um einen Speicherverlust zu verursachen.

Natürlich neigen alle Arten von Listenern (wie UI-Listener), Caches oder alle langlebigen gemeinsamen Zustände dazu, Speicherverluste zu verursachen, wenn sie nicht richtig behandelt werden. Es versteht sich, dass dies kein Java - Eckfall oder ein Problem mit dem Garbage Collector ist. Es ist ein Designproblem. Wir entwerfen, dass wir einem langlebigen Objekt einen Listener hinzufügen, aber den Listener nicht entfernen, wenn er nicht mehr benötigt wird. Wir haben Objekte im Cache, aber keine Strategie, um sie aus dem Cache zu entfernen.

Wir haben vielleicht ein komplexes Diagramm, das den vorherigen Zustand speichert, der für eine Berechnung benötigt wird. Aber der vorherige Zustand ist selbst mit dem vorherigen Zustand verbunden und so weiter.

Als müssten wir SQL-Verbindungen oder -Dateien schließen. Wir müssen korrekte Referenzen auf null setzen und Elemente aus der Sammlung entfernen. Wir werden geeignete Caching-Strategien haben (maximale Speichergröße, Anzahl der Elemente oder Timer). Alle Objekte, über die ein Listener benachrichtigt werden kann, müssen sowohl eine addListener- als auch eine removeListener-Methode bereitstellen. Und wenn diese Benachrichtiger nicht mehr verwendet werden, müssen sie ihre Listener-Liste löschen.

Ein Speicherverlust ist in der Tat wirklich möglich und ist perfekt vorhersehbar. Keine Notwendigkeit für spezielle Sprachfunktionen oder Eckfälle. Speicherverluste sind entweder ein Hinweis darauf, dass möglicherweise etwas fehlt, oder auch ein Hinweis auf Designprobleme.

189

Die Antwort hängt ganz davon ab, was der Interviewer zu fragen glaubte.

Ist es in der Praxis möglich, Java undicht zu machen? Natürlich ist es das und es gibt viele Beispiele in den anderen Antworten.

Aber es gibt mehrere Meta-Fragen, die möglicherweise gestellt wurden?

  • Ist eine theoretisch "perfekte" Java Implementierung anfällig für Lecks?
  • Versteht der Kandidat den Unterschied zwischen Theorie und Realität?
  • Versteht der Kandidat, wie die Speicherbereinigung funktioniert?
  • Oder wie soll die Müllabfuhr im Idealfall funktionieren?
  • Wissen sie, dass sie andere Sprachen über native Schnittstellen aufrufen können?
  • Wissen sie, Speicher in diesen anderen Sprachen zu verlieren?
  • Weiß der Kandidat überhaupt, was Speicherverwaltung ist und was sich hinter den Kulissen von Java abspielt?

Ich lese Ihre Meta-Frage als "Was ist eine Antwort, die ich in dieser Interview-Situation hätte verwenden können". Und deshalb werde ich mich auf Interviewfähigkeiten konzentrieren, anstatt auf Java. Ich glaube, Sie wiederholen eher die Situation, dass Sie die Antwort auf eine Frage in einem Interview nicht kennen, als dass Sie sich an einem Ort befinden müssen, an dem Sie wissen müssen, wie Sie Java auslaufen lassen. Hoffentlich wird dies helfen.

Eine der wichtigsten Fähigkeiten, die Sie für ein Vorstellungsgespräch entwickeln können, besteht darin, aktiv auf die Fragen zu hören und mit dem Interviewer zusammenzuarbeiten, um deren Absichten zu ermitteln. Auf diese Weise können Sie nicht nur ihre Fragen so beantworten, wie sie möchten, sondern auch zeigen, dass Sie über einige wichtige Kommunikationsfähigkeiten verfügen. Und wenn es um die Wahl zwischen vielen gleichermaßen talentierten Entwicklern geht, stelle ich denjenigen ein, der zuhört, denkt und versteht, bevor er jedes Mal reagiert.

152
PlayTank

Das Folgende ist ein ziemlich sinnloses Beispiel, wenn Sie JDBC nicht verstehen. Oder zumindest, wie JDBC erwartet, dass ein Entwickler die Instanzen Connection, Statement und ResultSet schließt, bevor er sie verwirft oder Verweise auf sie verliert, anstatt sich auf die Implementierung von finalize zu verlassen.

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);
   }
}

Das obige Problem ist, dass das Objekt Connection nicht geschlossen wird und daher die physische Verbindung offen bleibt, bis der Garbage Collector vorbeikommt und feststellt, dass es nicht erreichbar ist. GC ruft die Methode finalize auf, aber es gibt JDBC-Treiber, die finalize nicht implementieren, zumindest nicht auf die gleiche Weise wie Connection.close implementiert ist. Das resultierende Verhalten ist, dass während Speicher aufgrund von nicht erreichbaren Objekten, die gesammelt werden, zurückgefordert wird, Ressourcen (einschließlich Speicher), die dem Objekt Connection zugeordnet sind, möglicherweise einfach nicht zurückgefordert werden.

In einem solchen Fall, in dem die Methode Connection der Methode finalize nicht alles bereinigt, kann es vorkommen, dass die physische Verbindung zum Datenbankserver mehrere Garbage Collection-Zyklen dauert, bis der Datenbankserver schließlich feststellt, dass die Verbindung nicht aktiv ist (falls dies der Fall ist) tut), und sollte geschlossen werden.

Selbst wenn der JDBC-Treiber finalize implementieren würde, können während der Finalisierung Ausnahmen ausgelöst werden. Das resultierende Verhalten ist, dass jeglicher Speicher, der dem jetzt "ruhenden" Objekt zugeordnet ist, nicht zurückgefordert wird, da finalize garantiert nur einmal aufgerufen wird.

Das obige Szenario, in dem während der Objekt-Finalisierung Ausnahmen auftreten, hängt mit einem anderen Szenario zusammen, das möglicherweise zu einem Speicherverlust führen kann - der Auferstehung von Objekten. Objektauferstehung wird oft absichtlich durchgeführt, indem ein starker Verweis auf das Objekt erstellt wird, das von einem anderen Objekt finalisiert wird. Wenn die Auferstehung von Objekten missbraucht wird, führt dies in Kombination mit anderen Quellen von Speicherlecks zu einem Speicherverlust.

Es gibt noch viele weitere Beispiele, die Sie heraufbeschwören können

  • Verwalten einer List -Instanz, in der Sie nur der Liste hinzufügen und keine Elemente daraus löschen (obwohl Sie Elemente entfernen sollten, die Sie nicht mehr benötigen), oder
  • Öffnen Sie Sockets oder Files, schließen Sie sie jedoch nicht, wenn sie nicht mehr benötigt werden (ähnlich wie im obigen Beispiel für die Klasse Connection).
  • Singletons werden nicht entladen, wenn eine Java EE-Anwendung heruntergefahren wird. Anscheinend behält der Classloader, der die Singleton-Klasse geladen hat, einen Verweis auf die Klasse bei, und daher wird die Singleton-Instanz niemals gesammelt. Wenn eine neue Instanz der Anwendung bereitgestellt wird, wird normalerweise ein neuer Klassenladeprogramm erstellt, und das frühere Klassenladeprogramm bleibt aufgrund des Singleton weiterhin bestehen.
127
Vineet Reynolds

Wahrscheinlich eines der einfachsten Beispiele für einen möglichen Speicherverlust und wie man ihn vermeidet, ist die Implementierung von 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;
}

Wenn Sie es selbst implementiert hätten, hätten Sie gedacht, das Array-Element zu löschen, das nicht mehr verwendet wird (elementData[--size] = null)? Diese Referenz könnte ein riesiges Objekt am Leben erhalten ...

115
meriton

Jedes Mal, wenn Sie Verweise auf Objekte aufbewahren, die Sie nicht mehr benötigen, tritt ein Speicherverlust auf. In Behandeln von Speicherverlusten in Java Programmen finden Sie Beispiele dafür, wie sich Speicherverluste in Java manifestieren und was Sie dagegen tun können.

65
Bill the Lizard

Mit der Klasse Sun.misc.Unsafe kann ein Speicherverlust verursacht werden. Tatsächlich wird diese Serviceklasse in verschiedenen Standardklassen verwendet (zum Beispiel in Java.nio Klassen). Sie können keine Instanz dieser Klasse direkt erstellen, aber Sie können verwenden Sie Reflection, um dies zu tun.

Code wird in Eclipse nicht kompiliert IDE - kompilieren Sie ihn mit dem Befehl javac (während der Kompilierung erhalten Sie Warnungen)

import Java.lang.reflect.Constructor;
import Java.lang.reflect.Field;
import Sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("Sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}
49
stemm

Ich kann meine Antwort von hier kopieren: Einfachste Methode, um in Java einen Speicherverlust zu verursachen?

"Ein Speicherverlust in der Informatik (oder in diesem Zusammenhang ein Speicherverlust) tritt auf, wenn ein Computerprogramm Speicher verbraucht, ihn jedoch nicht an das Betriebssystem zurückgeben kann." (Wikipedia)

Die einfache Antwort lautet: Sie können nicht. Java führt die automatische Speicherverwaltung durch und gibt Ressourcen frei, die für Sie nicht benötigt werden. Sie können das nicht aufhalten. Es wird IMMER in der Lage sein, die Ressourcen freizugeben. Bei Programmen mit manueller Speicherverwaltung ist dies anders. In C kann man mit malloc () etwas Speicher bekommen. Um den Speicher freizugeben, benötigen Sie den von malloc zurückgegebenen Zeiger und rufen free () auf. Wenn Sie den Zeiger jedoch nicht mehr haben (überschrieben oder die Lebensdauer überschritten), können Sie diesen Speicher leider nicht mehr freigeben, und es liegt ein Speicherverlust vor.

Alle anderen Antworten sind in meiner Definition bisher nicht wirklich Speicherlecks. Sie alle zielen darauf ab, die Erinnerung wirklich schnell mit sinnlosen Dingen zu füllen. Sie können die von Ihnen erstellten Objekte aber jederzeit dereferenzieren und so den Speicher freigeben -> NO LEAK. acconrads antwort kommt allerdings ziemlich nahe, wie ich zugeben muss, da seine lösung darin besteht, den müllsammler einfach "zum absturz zu bringen", indem er ihn in eine endlose schleife zwingt).

Die lange Antwort lautet: Sie können einen Speicherverlust bekommen, indem Sie eine Bibliothek für Java mit der JNI schreiben, die eine manuelle Speicherverwaltung und damit Speicherverluste aufweisen kann. Wenn Sie diese Bibliothek aufrufen, führt Ihr Prozess Java zu einem Speicherverlust. Sie können auch Fehler in der JVM haben, sodass die JVM Speicherplatz verliert. Es gibt wahrscheinlich Fehler in der JVM, es kann sogar einige bekannte geben, da die Garbage Collection nicht so trivial ist, aber dann ist es immer noch ein Fehler. Konstruktionsbedingt ist dies nicht möglich. Möglicherweise fragen Sie nach einem Java Code, der von einem solchen Fehler betroffen ist. Tut mir leid, ich kenne keinen und es könnte in der nächsten Java Version sowieso kein Fehler mehr sein.

43
yankee

Hier ist eine einfache/finstere über http://wiki.Eclipse.org/Performance_Bloopers#String.substring.28.29 .

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Da sich der Teilstring auf die interne Darstellung des ursprünglichen, viel längeren Strings bezieht, bleibt das Original im Speicher. So haben Sie, solange Sie einen StringLeaker im Spiel haben, auch die gesamte ursprüngliche Zeichenfolge im Speicher, auch wenn Sie vielleicht glauben, nur an einer Zeichenfolge festzuhalten.

Um zu vermeiden, dass ein unerwünschter Verweis auf die ursprüngliche Zeichenfolge gespeichert wird, gehen Sie folgendermaßen vor:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

Für zusätzliche Fehler können Sie auch die Unterzeichenfolge .intern() eingeben:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Dadurch bleiben sowohl die ursprüngliche lange Zeichenfolge als auch die abgeleitete Teilzeichenfolge im Speicher, auch nachdem die StringLeaker-Instanz verworfen wurde.

36
Jon Chambers

Nehmen Sie eine beliebige Webanwendung mit, die in einem beliebigen Servlet-Container ausgeführt wird (Tomcat, Jetty, Glassfish usw.). Stellen Sie die App 10 oder 20 Mal hintereinander erneut bereit (es kann ausreichen, einfach die WAR-Datei im Autodeploy-Verzeichnis des Servers zu berühren.

Wenn dies nicht tatsächlich von jemandem getestet wurde, ist die Wahrscheinlichkeit hoch, dass Sie nach einigen erneuten Bereitstellungen einen OutOfMemoryError erhalten, da die Anwendung sich nicht darum gekümmert hat, nach sich selbst aufzuräumen. Möglicherweise finden Sie mit diesem Test sogar einen Fehler auf Ihrem Server.

Das Problem ist, dass die Lebensdauer des Containers länger ist als die Lebensdauer Ihrer Anwendung. Sie müssen sicherstellen, dass alle Verweise, die der Container möglicherweise auf Objekte oder Klassen Ihrer Anwendung enthält, müllsammelbar sind.

Wenn es nur eine Referenz gibt, die die Aufhebung Ihrer Web-App überlebt, kann der entsprechende Klassenladeprogramm und folglich nicht alle Klassen Ihrer Web-App müllsammeln.

Von Ihrer Anwendung gestartete Threads, ThreadLocal-Variablen und Protokollierungs-Appender sind einige der üblichen Verdächtigen, die zu Classloader-Lecks führen können.

35
Harald Wellmann

Ein häufiges Beispiel hierfür im GUI-Code ist das Erstellen eines Widgets/einer Komponente und das Hinzufügen eines Listeners zu einem statischen/anwendungsbezogenen Objekt, wobei der Listener dann nicht entfernt wird, wenn das Widget zerstört wird. Sie bekommen nicht nur einen Speicherverlust, sondern auch einen Leistungseinbruch, da alle Ihre alten Zuhörer auch angerufen werden, wenn Sie Ereignisse abfeuern.

35
pauli

Vielleicht durch die Verwendung von externem nativem Code über JNI?

Mit reinem Java ist das fast unmöglich.

Dabei handelt es sich jedoch um einen "Standard" -Speicherverlust, bei dem Sie nicht mehr auf den Speicher zugreifen können, der sich jedoch immer noch im Besitz der Anwendung befindet. Sie können stattdessen Verweise auf nicht verwendete Objekte beibehalten oder Streams öffnen, ohne sie anschließend zu schließen.

33
Rogach

Ich hatte einmal ein nettes "Memory Leak" in Bezug auf PermGen und XML-Parsing. Der von uns verwendete XML-Parser (ich kann mich nicht erinnern, welcher es war) hat eine String.intern () für Tag-Namen erstellt, um den Vergleich zu beschleunigen. Einer unserer Kunden hatte die großartige Idee, Datenwerte nicht in XML-Attributen oder Text, sondern als Tag-Namen zu speichern. Daher hatten wir ein Dokument wie das Folgende:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

Tatsächlich verwendeten sie keine Zahlen, sondern längere Text-IDs (etwa 20 Zeichen), die eindeutig waren und mit einer Rate von 10 bis 15 Millionen pro Tag eingingen. Das sind 200 MB Müll pro Tag, der nie mehr gebraucht und nie mehr gecodet wird (da er in PermGen ist). Wir hatten permgen auf 512 MB eingestellt, so dass es ungefähr zwei Tage dauerte, bis die Out-of-Memory-Ausnahme (OOME) eintraf ...

29
Ron

Was ist ein Speicherverlust:

  • Es ist durch ein Fehler oder schlechtes Design. verursacht
  • Es ist eine Verschwendung von Gedächtnis.
  • Es wird mit der Zeit schlimmer.
  • Der Garbage Collector kann es nicht säubern.

Typisches Beispiel:

Ein Cache mit Objekten ist ein guter Ausgangspunkt, um Dinge durcheinander zu bringen.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Dein Cache wächst und wächst. Und ziemlich bald wird die gesamte Datenbank in den Speicher gesaugt. Ein besseres Design verwendet eine LRUMap (hält nur kürzlich verwendete Objekte im Cache).

Klar, Sie können die Dinge viel komplizierter machen:

  • verwenden von ThreadLocal -Konstruktionen.
  • hinzufügen weiterer komplexer Referenzbäume.
  • oder Undichtigkeiten durch Bibliotheken von Drittanbietern.

Was häufig passiert:

Wenn dieses Info-Objekt Verweise auf andere Objekte enthält, die wiederum Verweise auf andere Objekte enthalten. In gewisser Weise könnte man dies auch als eine Art Speicherverlust betrachten (verursacht durch schlechtes Design).

23
bvdb

Ich bin vor kurzem auf eine Speicherverlustsituation gestoßen, die auf eine Weise von log4j verursacht wurde.

Log4j verfügt über diesen Mechanismus mit dem Namen NDC = Nested Diagnostic Context , mit dem verschachtelte Protokollausgaben aus verschiedenen Quellen unterschieden werden können. Die Granularität, mit der NDC arbeitet, hängt von Threads ab, sodass die Protokollausgaben von verschiedenen Threads separat unterschieden werden.

Um threadspezifische Tags zu speichern, verwendet die NDC-Klasse von log4j eine Hashtable, die vom Thread-Objekt selbst (im Gegensatz zur Thread-ID) verschlüsselt wird, und zwar so lange, bis das NDC-Tag alle Objekte im Speicher hat, die vom Thread hängen Objekt bleiben auch in Erinnerung. In unserer Webanwendung verwenden wir NDC, um Protokollausgaben mit einer Anforderungs-ID zu kennzeichnen, um Protokolle von einer einzelnen Anforderung separat zu unterscheiden. Der Container, der das NDC-Tag mit einem Thread verknüpft, entfernt es auch, während die Antwort von einer Anforderung zurückgegeben wird. Das Problem trat auf, als während der Verarbeitung einer Anforderung ein untergeordneter Thread erzeugt wurde, etwa der folgende Code:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Daher wurde ein NDC-Kontext mit einem Inline-Thread verknüpft, der erzeugt wurde. Das Thread-Objekt, das der Schlüssel für diesen NDC-Kontext war, ist der Inline-Thread, an dem das hugeList-Objekt hängt. Selbst nachdem der Thread das getan hat, was er getan hat, wurde der Verweis auf die riesige Liste durch den NDC-Kontext Hastable am Leben gehalten, wodurch ein Speicherverlust verursacht wurde.

22
Puneet

Ich fand es interessant, dass niemand die internen Klassenbeispiele verwendete. Wenn Sie eine interne Klasse haben; Es enthält von Natur aus einen Verweis auf die enthaltende Klasse. Natürlich handelt es sich technisch gesehen nicht um einen Speicherverlust, da Java ihn letztendlich aufräumt. Dies kann jedoch dazu führen, dass Klassen länger als erwartet herumhängen.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Wenn Sie jetzt Example1 aufrufen und Example2 verwerfen, haben Sie immer noch eine Verknüpfung zu einem Example1-Objekt.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Ich habe auch ein Gerücht gehört, dass, wenn Sie eine Variable haben, die länger als eine bestimmte Zeitspanne existiert; Java geht davon aus, dass es immer existiert und tatsächlich niemals versucht, es zu bereinigen, wenn es im Code nicht mehr erreichbar ist. Das ist aber völlig unbestätigt.

22
Suroot

Der Interviewer suchte wahrscheinlich nach einem Zirkelverweis wie dem folgenden Code (der im Übrigen nur in sehr alten JVMs, die die Referenzzählung verwendeten, Speicher verliert, was nicht mehr der Fall ist). Aber es ist eine ziemlich vage Frage, daher ist es eine hervorragende Gelegenheit, Ihr Verständnis der JVM-Speicherverwaltung zu demonstrieren.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Dann können Sie erklären, dass mit der Referenzzählung der obige Code Speicher verlieren würde. Aber die meisten modernen JVMs verwenden keine Referenzzählung mehr, die meisten verwenden einen Sweep-Garbage-Collector, der tatsächlich diesen Speicher sammelt.

Als Nächstes könnten Sie das Erstellen eines Objekts mit einer zugrunde liegenden nativen Ressource wie folgt erläutern:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Dann können Sie erklären, dass dies technisch gesehen ein Speicherverlust ist, der jedoch tatsächlich durch systemeigenen Code in der JVM verursacht wird, der zugrunde liegende systemeigene Ressourcen zuweist, die nicht durch Ihren Code Java freigegeben wurden.

Letztendlich müssen Sie mit einer modernen JVM Java Code schreiben, der eine native Ressource außerhalb des normalen Bereichs der JVM-Kenntnisse zuweist.

19
deltamind106

Erstellen Sie eine statische Karte, und fügen Sie ihr weitere Referenzen hinzu. Diese werden niemals gcd werden.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
18
duffymo

Jeder vergisst immer die native Code-Route. Hier ist eine einfache Formel für ein Leck:

  1. Deklarieren Sie die native Methode.
  2. Rufen Sie in der systemeigenen Methode malloc auf. Rufen Sie nicht free an.
  3. Rufen Sie die native Methode auf.

Denken Sie daran, dass die Speicherzuweisungen im systemeigenen Code vom JVM-Heap stammen.

17
Paul Morie

Sie können einen beweglichen Speicherverlust erstellen, indem Sie eine neue Instanz einer Klasse in der finalize-Methode dieser Klasse erstellen. Bonuspunkte, wenn der Finalizer mehrere Instanzen erstellt. Hier ist ein einfaches Programm, das den gesamten Heap-Speicher abhängig von Ihrer Heap-Größe in einigen Sekunden bis zu einigen Minuten verliert:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}
16
sethobrien

Ich glaube, das hat noch niemand gesagt: Sie können ein Objekt wiederbeleben, indem Sie die Methode finalize () so überschreiben, dass finalize () irgendwo eine Referenz darauf speichert. Der Garbage Collector wird nur einmal für das Objekt aufgerufen, sodass das Objekt danach niemals zerstört wird.

15
Ben

Kürzlich bin ich auf eine subtilere Art von Ressourcenleck gestoßen. Wir öffnen Ressourcen über getResourceAsStream des Klassenladers und es kam vor, dass die Eingabestream-Handles nicht geschlossen wurden.

Ähm, könnte man sagen, was für ein Idiot.

Das Interessante daran ist: Auf diese Weise können Sie den Heap-Speicher des zugrunde liegenden Prozesses verlieren und nicht den Heap-Speicher von JVM.

Alles, was Sie brauchen, ist eine JAR-Datei mit einer Datei, auf die aus dem Code Java verwiesen wird. Je größer die JAR-Datei ist, desto schneller wird Speicher zugewiesen.

Sie können ein solches Glas einfach mit der folgenden Klasse erstellen:

import Java.io.File;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.util.Zip.ZipEntry;
import Java.util.Zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Fügen Sie es einfach in eine Datei mit dem Namen BigJarCreator.Java ein, kompilieren Sie es und führen Sie es über die Befehlszeile aus:

javac BigJarCreator.Java
java -cp . BigJarCreator

Et voilà: Sie finden ein JAR-Archiv in Ihrem aktuellen Arbeitsverzeichnis mit zwei darin enthaltenen Dateien.

Lassen Sie uns eine zweite Klasse erstellen:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Diese Klasse führt im Grunde nichts aus, sondern erstellt nicht referenzierte InputStream-Objekte. Diese Objekte werden sofort mit Müll entsorgt und tragen somit nicht zur Größe des Heapspeichers bei. Für unser Beispiel ist es wichtig, eine vorhandene Ressource aus einer JAR-Datei zu laden, und hier spielt die Größe eine Rolle!

Wenn Sie Zweifel haben, versuchen Sie, die oben genannte Klasse zu kompilieren und zu starten. Achten Sie jedoch darauf, eine angemessene Heap-Größe (2 MB) zu wählen:

javac MemLeak.Java
java -Xmx2m -classpath .:big.jar MemLeak

Hier tritt kein OOM-Fehler auf, da keine Referenzen gespeichert sind. Die Anwendung wird weiterhin ausgeführt, unabhängig davon, wie groß Sie im obigen Beispiel ITERATIONS ausgewählt haben. Der Speicherverbrauch Ihres Prozesses (sichtbar oben (RES/RSS) oder Prozess-Explorers) nimmt zu, es sei denn, die Anwendung erhält den Befehl wait. In der obigen Konfiguration werden ungefähr 150 MB Arbeitsspeicher zugewiesen.

Wenn Sie möchten, dass die Anwendung auf Nummer sicher geht, schließen Sie den Eingabestream genau dort, wo er erstellt wurde:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

und Ihr Prozess wird 35 MB nicht überschreiten, unabhängig von der Anzahl der Iterationen.

Ganz einfach und überraschend.

15
Jay

Wie viele Leute bereits angedeutet haben, sind Ressourcenlecks ziemlich einfach zu verursachen - wie die JDBC-Beispiele. Tatsächliche Speicherverluste sind etwas schwieriger - vor allem, wenn Sie sich nicht darauf verlassen müssen, dass Teile der JVM dies für Sie tun ...

Die Idee, Objekte mit einer sehr großen Stellfläche zu erstellen und dann nicht darauf zugreifen zu können, ist ebenfalls kein wirklicher Speicherleck. Wenn nichts darauf zugreifen kann, wird Müll gesammelt, und wenn etwas darauf zugreifen kann, ist es kein Leck ...

Eine Möglichkeit, wie verwendet funktioniert - und ich weiß nicht, ob es immer noch funktioniert - besteht darin, eine dreiteilige Kreiskette zu haben. Wie in Objekt A hat ein Verweis auf Objekt B, hat Objekt B einen Verweis auf Objekt C und Objekt C hat einen Verweis auf Objekt A. Der GC war klug genug zu wissen, dass eine zwei tiefe Kette - wie in A <-> B - Kann sicher eingesammelt werden, wenn A und B für nichts anderes zugänglich sind, aber nicht mit der Dreiwege-Kette umgehen können ...

14
Graham

es gibt viele verschiedene Situationen, in denen der Speicher ausläuft. Eine, auf die ich gestoßen bin, enthüllt eine Karte, die nicht enthüllt und an anderer Stelle verwendet werden sollte.

public class ServiceFactory {

private Map<String, Service> services;

private static ServiceFactory singleton;

private ServiceFactory() {
    services = new HashMap<String, Service>();
}

public static synchronized ServiceFactory getDefault() {

    if (singleton == null) {
        singleton = new ServiceFactory();
    }
    return singleton;
}

public void addService(String name, Service serv) {
    services.put(name, serv);
}

public void removeService(String name) {
    services.remove(name);
}

public Service getService(String name, Service serv) {
    return services.get(name);
}

// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose 
public Map<String, Service> getAllServices() {
    return services;
}

}

// resource class is a heavy class
class Service {

}
11
Ben Xu

Threads werden nicht gesammelt, bis sie enden. Sie dienen als Wurzeln der Speicherbereinigung. Sie sind eines der wenigen Objekte, die nicht einfach dadurch zurückgefordert werden können, dass man sie vergisst oder Verweise auf sie löscht.

Beachten Sie: Das Grundmuster zum Beenden eines Arbeitsthreads besteht darin, eine Bedingungsvariable festzulegen, die vom Thread gesehen wird. Der Thread kann die Variable regelmäßig überprüfen und als Signal zum Beenden verwenden. Wenn die Variable nicht als volatile deklariert ist, wird die Änderung an der Variablen möglicherweise nicht vom Thread gesehen, sodass er nicht weiß, ob sie beendet werden soll. Oder stellen Sie sich vor, einige Threads möchten ein freigegebenes Objekt aktualisieren, aber beim Versuch, es zu sperren, einen Deadlock ausführen.

Wenn Sie nur eine Handvoll Threads haben, werden diese Fehler wahrscheinlich offensichtlich sein, weil Ihr Programm nicht mehr richtig funktioniert. Wenn Sie über einen Thread-Pool verfügen, der nach Bedarf weitere Threads erstellt, werden die veralteten/blockierten Threads möglicherweise nicht bemerkt und sammeln sich auf unbestimmte Zeit an, was zu einem Speicherverlust führt. Threads verwenden wahrscheinlich andere Daten in Ihrer Anwendung, sodass auch verhindert wird, dass Daten, auf die sie direkt verweisen, jemals erfasst werden.

Als Spielzeugbeispiel:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Rufen Sie System.gc() auf, wie Sie möchten, aber das an leakMe übergebene Objekt wird niemals sterben.

(* bearbeitet *)

11
Boann

Ich denke, dass ein gültiges Beispiel die Verwendung von ThreadLocal-Variablen in einer Umgebung sein könnte, in der Threads gepoolt werden.

Verwenden Sie beispielsweise ThreadLocal-Variablen in Servlets, um mit anderen Webkomponenten zu kommunizieren. Dabei werden die Threads vom Container erstellt und die inaktiven in einem Pool verwaltet. ThreadLocal-Variablen werden dort gespeichert, wenn sie nicht ordnungsgemäß bereinigt wurden, bis möglicherweise dieselbe Webkomponente ihre Werte überschreibt.

Sobald das Problem erkannt wurde, kann es natürlich leicht gelöst werden.

10
mschonaker

Der Interviewer hat möglicherweise nach einer zirkulären Referenzlösung gesucht:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Dies ist ein klassisches Problem bei der Referenzzählung von Müllsammlern. Sie würden dann höflich erklären, dass JVMs einen viel ausgefeilteren Algorithmus verwenden, der diese Einschränkung nicht aufweist.

-Wes Tarle

10
Wesley Tarle

Eine andere Möglichkeit, potenziell große Speicherverluste zu verursachen, besteht darin, Verweise auf Map.Entry<K,V> eines TreeMap zu speichern.

Es ist schwer einzuschätzen, warum dies nur für TreeMaps gilt, aber bei Betrachtung der Implementierung könnte der Grund sein, dass: ein TreeMap.Entry Verweise auf seine Geschwister speichert, wenn also ein TreeMap bereit ist gesammelt werden, aber eine andere Klasse enthält einen Verweis auf einen ihrer Map.Entry, dann wird die gesamte Map im Speicher behalten.


Reales Szenario:

Stellen Sie sich eine Datenbankabfrage vor, die eine große TreeMap Datenstruktur zurückgibt. Normalerweise wird TreeMaps verwendet, da die Reihenfolge für das Einfügen von Elementen beibehalten wird.

public static Map<String, Integer> pseudoQueryDatabase();

Wenn die Abfrage viele Male aufgerufen wurde und Sie für jede Abfrage (also für jede zurückgegebene Map) irgendwo eine Entry speichern, wächst der Speicher ständig.

Betrachten Sie die folgende Wrapper-Klasse:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Anwendung:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

Nach jedem pseudoQueryDatabase() -Aufruf sollten die map -Instanzen zur Erfassung bereit sein, dies geschieht jedoch nicht, da mindestens eine Entry an einer anderen Stelle gespeichert wird.

Abhängig von Ihren jvm Einstellungen kann die Anwendung im frühen Stadium aufgrund eines OutOfMemoryError abstürzen.

Sie können aus dieser visualvm Grafik ersehen, wie der Speicher weiter wächst.

Memory dump - TreeMap

Das gleiche passiert nicht mit einer Hash-Datenstruktur (HashMap).

Dies ist die Grafik bei Verwendung eines HashMap.

Memory dump - HashMap

Die Lösung? Speichern Sie einfach direkt den Schlüssel/Wert (wie Sie es wahrscheinlich bereits tun), anstatt den Map.Entry zu speichern.


Ich habe einen umfangreicheren Benchmark geschrieben hier .

10
Marko Pacak

Ein Beispiel, das ich kürzlich behoben habe, ist das Erstellen neuer GC- und Image-Objekte, aber das Vergessen, die dispose () -Methode aufzurufen.

GC-Javadoc-Snippet:

Der Anwendungscode muss die GC.dispose () - Methode explizit aufrufen, um die von jeder Instanz verwalteten Betriebssystemressourcen freizugeben, wenn diese Instanzen nicht mehr benötigt werden. Dies ist besonders unter Windows 95 und Windows 98 wichtig, wenn für das Betriebssystem eine begrenzte Anzahl von Gerätekontexten verfügbar ist.

Bild-Javadoc-Snippet:

Der Anwendungscode muss die Image.dispose () - Methode explizit aufrufen, um die von jeder Instanz verwalteten Betriebssystemressourcen freizugeben, wenn diese Instanzen nicht mehr benötigt werden.

9
Scott

Ich möchte einen Rat geben, wie die Anwendung mit den in JVM verfügbaren Tools auf Speicherverluste überwacht werden kann. Es wird nicht gezeigt, wie ein Speicherverlust erzeugt wird, sondern es wird erläutert, wie der Speicherverlust mit nur wenigen verfügbaren Tools erkannt wird.

Sie müssen zuerst den Speicherverbrauch von Java überwachen.

Die einfachste Möglichkeit hierfür ist die Verwendung des mit JVM gelieferten Dienstprogramms jstat.

jstat -gcutil <process_id> <timeout>

Es wird der Speicherverbrauch für jede Generation (Jung, Alt und Alt) und die Speicherbereinigungszeiten (Jung und Voll) gemeldet.

Sobald Sie feststellen, dass die vollständige Speicherbereinigung zu oft ausgeführt wird und zu viel Zeit in Anspruch nimmt, können Sie davon ausgehen, dass bei der Anwendung Speicherplatz verloren geht.

Dann müssen Sie einen Speicherauszug mit dem Dienstprogramm jmap erstellen:

jmap -dump:live,format=b,file=heap.bin <process_id>

Dann müssen Sie die Datei heap.bin beispielsweise mit Memory Analyzer, Eclipse Memory Analyzer (MAT) analysieren.

MAT analysiert den Speicher und gibt Ihnen verdächtige Informationen zu Speicherlecks.

8
Pavel Molchanov

Theoretisch geht das nicht. Java Speichermodell verhindert es. Da jedoch Java implementiert werden muss, gibt es einige Einschränkungen, die Sie verwenden können. Hängt davon ab, was Sie verwenden können:

  • Wenn Sie native verwenden können, können Sie Speicher zuweisen, den Sie später nicht mehr freigeben.

  • Wenn das nicht verfügbar ist, gibt es ein schmutziges kleines Geheimnis über Java, das nicht viele Leute kennen. Sie können nach einem Direktzugriffsarray fragen, das nicht vom GC verwaltet wird, und es kann daher leicht verwendet werden, um ein Speicherverlust zu verursachen. Dies wird von DirectByteBuffer bereitgestellt (http://download.Oracle.com/javase/1.5.0/docs/api/Java/nio/ByteBuffer.html#allocateDirect(int)).

  • Wenn Sie keines davon verwenden können, können Sie trotzdem einen Speicherverlust verursachen, indem Sie den GC austricksen. Die JVM wird mithilfe einer Garbage Collection von Generational implementiert. Dies bedeutet, dass der Haufen in Bereiche unterteilt ist: Junge, Erwachsene und Älteste. Ein Objekt, wenn es im jungen Bereich entsteht. Da er immer mehr benutzt wird, entwickelt er sich zu Erwachsenen bis hin zu Ältesten. Ein Objekt, das höchstwahrscheinlich in den Bereich der älteren Bevölkerung gelangt, wird nicht als Müll gesammelt. Sie können nicht sicher sein, dass ein Objekt durchgesickert ist. Wenn Sie nach einem Stopp und einer GC-Reinigung fragen, kann dies dazu führen, dass das Objekt zwar durchgesickert ist, aber über einen längeren Zeitraum hinweg. Weitere Informationen unter (http://Java.Sun.com/docs/hotspot/gc1.4.2/faq.html)

  • Außerdem müssen Klassenobjekte nicht gecodet werden. Könnte mir ein Weg sein, es zu tun.

8
Artur Ventura

Ein Thread, der nicht beendet wird (z. B. in seiner Ausführungsmethode auf unbestimmte Zeit). Es wird kein Müll gesammelt, auch wenn wir einen Verweis darauf verlieren. Sie können Felder hinzufügen, damit das Thread-Objekt so groß wird, wie Sie möchten.

Die derzeit beste Antwort listet weitere Tricks auf, die jedoch überflüssig erscheinen.

8
h22

Wirft eine nicht behandelte Ausnahme von der finalize-Methode.

7
Basanth Roy

Die meisten Speicherverluste, die ich in Java gesehen habe, betreffen Prozesse, die nicht mehr synchron sind.

Prozess A spricht über TCP mit B und weist Prozess B an, etwas zu erstellen. B gibt der Ressource eine ID aus, z. B. 432423, die A in einem Objekt speichert und verwendet, während er mit B spricht. Irgendwann wird das Objekt in A von der Garbage Collection zurückgefordert (möglicherweise aufgrund eines Fehlers), aber A teilt B niemals mit, dass ( vielleicht noch ein Bug).

Jetzt hat A nicht mehr die ID des Objekts, das es in Bs RAM erstellt hat, und B weiß nicht, dass A keinen Verweis mehr auf das Objekt hat. In der Tat ist das Objekt durchgesickert.

7
arnt

Es gibt viele Antworten zum Erstellen eines Speicherverlusts in Java. Beachten Sie jedoch den im Interview gestellten Punkt.

"Wie erstelle ich ein Speicherleck mit Java?" ist eine offene Frage, deren Zweck es ist, den Grad der Erfahrung eines Entwicklers zu bewerten.

Wenn ich Sie frage, ob Sie Erfahrung mit der Behebung von Speicherlecks in Java haben, lautet Ihre Antwort "Ja". Ich müsste dann mit "Könnten Sie mir Beispiele geben, wo Sie Speicherlecks beheben müssen?", Zu denen Sie mir ein oder zwei Beispiele geben würden.

Wenn der Interviewer jedoch fragt, wie mit Java ein Speicherverlust verursacht werden soll? Die erwartete Antwort sollte ungefähr so ​​lauten:

  • Ich habe einen Speicherverlust festgestellt ... (sagen Sie, wann) [das zeigt mir Erfahrung]
  • Der Code, der es verursacht hat, war ... (Code erklären) [Sie haben es selbst behoben]
  • Der Fix, den ich angewendet habe, basierte auf ... (Fix erklären) [Dies gibt mir die Möglichkeit, Einzelheiten zum Fix zu erfragen.]
  • Der Test, den ich gemacht habe, war ... [gibt mir die Möglichkeit, nach anderen Testmethoden zu fragen]
  • Ich habe es so dokumentiert ... [zusätzliche Punkte. Gut, wenn Sie es dokumentiert haben]
  • Wenn wir also in umgekehrter Reihenfolge vorgehen, also den von mir reparierten Code erhalten und meinen Fix entfernen, ist es vernünftig zu glauben, dass ein Speicherverlust vorliegt.

Wenn der Entwickler diesen Gedanken nicht befolgt, versuche ich, ihn/sie anzuleiten und zu fragen: "Können Sie mir ein Beispiel geben, wie Java Speicher lecken kann?", Gefolgt von "Mussten Sie jemals einen Speicherleck beheben?" in Java? "

Beachten Sie, dass ich nicht nach einem Beispiel zum Speicherleck in Java frage. Das wäre albern. Wer wäre an einem Entwickler interessiert, der effektiv Code schreiben kann, der den Speicher verliert?

7

Wenn die maximale Heap-Größe X. Y1 .... Yn Anzahl der Instanzen ist, ist der Gesamtspeicher = Anzahl der Instanzen X Bytes pro Instanz. Wenn X1 ...... Xn Bytes pro Instanzen ist, ist der Gesamtspeicher (M) = Y1 * X1 + ..... + Yn * Xn. Wenn also M> X ist, überschreitet es den Heap-Platz. Im Folgenden können die Probleme in Code 1 aufgeführt werden. Verwenden Sie mehr Instanzen als lokale. 2. Jedes Mal Instanzen erstellen, anstatt Objekte zu bündeln. 3.Nicht Erstellen Sie das Objekt auf Anfrage. 4.Nullsetzen der Objektreferenz nach Abschluss des Vorgangs.Wiederholen, wenn es im Programm angefordert wird.

7

Ein paar Vorschläge:

  • verwenden Sie Commons-Logging in einem Servlet-Container (ein bisschen provokativ vielleicht)
  • starten Sie einen Thread in einem Servlet-Container und kehren Sie nicht von seiner Ausführungsmethode zurück
  • lade animierte Gifs in einen Servlet-Container (dies startet einen Animationsthread)

Die oben genannten Effekte könnten durch erneutes Bereitstellen der Anwendung "verbessert" werden.

Kürzlich darauf gestoßen:

  • Aufruf von "new Java.util.Zip.Inflater ();" ohne jemals "Inflater.end ()" aufzurufen

Lesen Sie http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=5072161 und verknüpfen Sie die Themen für eine ausführliche Diskussion.

7
Peter

Eine Möglichkeit besteht darin, einen Wrapper für eine ArrayList zu erstellen, der nur eine Methode bereitstellt: eine, die der ArrayList Dinge hinzufügt. Machen Sie die ArrayList selbst privat. Erstellen Sie nun eines dieser Wrapper-Objekte im globalen Bereich (als statisches Objekt in einer Klasse) und qualifizieren Sie es mit dem Schlüsselwort final (z. B. public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()). Daher kann die Referenz jetzt nicht geändert werden. Das heißt, wrapperClass = null funktioniert nicht und kann nicht zum Freigeben des Speichers verwendet werden. Es gibt aber auch keine andere Möglichkeit, mit wrapperClass etwas anderes zu tun, als Objekte hinzuzufügen. Daher können Objekte, die Sie zu wrapperClass hinzufügen, nicht recycelt werden.

6
Gravity

Ein Speicherverlust in Java ist kein typischer C/C++ - Speicherverlust.

Um zu verstehen, wie die JVM funktioniert, lesen Sie Grundlegendes zur Speicherverwaltung .

Grundsätzlich ist der wichtige Teil:

Das Mark and Sweep-Modell

Die JRockit-JVM verwendet das Markierungs- und Sweep-Garbage-Collection-Modell, um Garbage-Collections für den gesamten Heap durchzuführen. Eine Markierungs- und Sweep-Speicherbereinigung besteht aus zwei Phasen, der Markierungsphase und der Sweep-Phase.

Während der Markierungsphase werden alle Objekte, die über Java Threads, native Handles und andere Stammquellen erreichbar sind, ebenfalls als lebendig markiert als die Objekte, die von diesen Objekten aus erreichbar sind und so weiter. Dieser Prozess identifiziert und markiert alle Objekte, die noch verwendet werden, und der Rest kann als Müll betrachtet werden.

Während der Sweep-Phase wird der Heap durchlaufen, um die Lücken zwischen den lebenden Objekten zu finden. Diese Lücken werden in einer freien Liste erfasst und für eine neue Objektzuordnung zur Verfügung gestellt.

Die JRockit JVM verwendet zwei verbesserte Versionen des Mark- und Sweep-Modells. Eine ist meist gleichzeitig Mark und Sweep und die andere ist parallel Mark und Sweep. Sie können die beiden Strategien auch mischen, indem Sie z. B. meist gleichzeitig Markierungen und parallele Sweeps ausführen.

So erstellen Sie ein Speicherleck in Java; Der einfachste Weg, dies zu tun, besteht darin, eine Datenbankverbindung zu erstellen, etwas zu arbeiten und sie einfach nicht zu Close() zu machen. Erstellen Sie dann eine neue Datenbankverbindung, während Sie im Gültigkeitsbereich bleiben. Dies ist zum Beispiel in einer Schleife nicht schwer zu bewerkstelligen. Wenn Sie einen Worker haben, der aus einer Warteschlange abruft und in eine Datenbank pusht, können Sie leicht einen Speicherverlust verursachen, indem Sie Close() -Verbindungen vergessen oder sie öffnen, wenn dies nicht erforderlich ist, und so weiter.

Schließlich verbrauchen Sie den der JVM zugewiesenen Heap, indem Sie die Verbindung Close() vergessen. Dies führt dazu, dass sich der JVM-Müll wie verrückt ansammelt. Dies kann zu Java.lang.OutOfMemoryError: Java heap space Fehlern führen. Es ist zu beachten, dass der Fehler möglicherweise nicht bedeutet, dass ein Speicherverlust vorliegt. es könnte nur bedeuten, dass Sie nicht genug Gedächtnis haben; Datenbanken wie Cassandra und ElasticSearch können diesen Fehler auslösen, weil sie nicht über genügend Speicherplatz verfügen.

Es ist erwähnenswert, dass dies für alle GC-Sprachen gilt. Nachfolgend einige Beispiele, die ich als SRE gesehen habe:

  • Knoten, der Redis als Warteschlange verwendet; Das Entwicklungsteam stellte alle 12 Stunden neue Verbindungen her und vergaß, die alten zu schließen. Schließlich war der Knoten OOMd, weil er den gesamten Speicher verbraucht hat.
  • Golang (ich bin an diesem schuld); Analysieren Sie große JSON-Dateien mit json.Unmarshal, übergeben Sie die Ergebnisse als Referenz und lassen Sie sie geöffnet. Letztendlich führte dies dazu, dass der gesamte Haufen durch versehentliche Refs verbraucht wurde, die ich offen hielt, um json zu dekodieren.
6
user1529891

String.substring-Methode in Java 1.6 erstellt einen Speicherverlust. Dieser Blog-Beitrag erklärt es.

http://javarevisited.blogspot.com/2011/10/how-substring-in-Java-works.html

5
Viraj

In Java ist ein "Speicherverlust" in erster Linie, dass Sie zu viel Speicher verwenden, der sich von dem in C unterscheidet, in dem Sie den Speicher nicht mehr verwenden, sondern vergessen, ihn zurückzugeben (freizugeben). Wenn ein Interviewer nach Java Speicherlecks fragt, fragt er, ob der JVM-Speicher immer mehr genutzt wird, und stellt fest, dass ein regelmäßiger Neustart der JVM die beste Lösung ist. (es sei denn, der Interviewer ist extrem technisch versiert)

Beantworten Sie diese Frage also so, als würden Sie fragen, warum die JVM-Speichernutzung im Laufe der Zeit zunimmt. Gute Antworten wären das Speichern zu vieler Daten in einer HttpSession mit zu langer Zeitüberschreitung oder ein schlecht implementierter In-Memory-Cache (Singleton), der niemals alte Einträge löscht. Eine weitere mögliche Antwort besteht darin, viele JSPs oder dynamisch generierte Klassen zu haben. Klassen werden in einen Speicherbereich namens PermGen geladen, der normalerweise klein ist, und die meisten JVMs implementieren kein Entladen von Klassen.

5
Chase

Swing hat es sehr einfach mit Dialogen. Erstellen Sie einen JDialog, zeigen Sie ihn, der Benutzer schließt ihn, undicht! Sie müssen dispose() aufrufen oder setDefaultCloseOperation(DISPOSE_ON_CLOSE) konfigurieren

5
jorgeu

Lapsed Listerners ist ein gutes Beispiel für Speicherverluste: Object wird als Listener hinzugefügt. Alle Verweise auf das Objekt werden auf Null gesetzt, wenn das Objekt nicht mehr benötigt wird. Wenn Sie jedoch vergessen, das Objekt aus der Listener-Liste zu entfernen, bleibt das Objekt am Leben und reagiert sogar auf Ereignisse, wodurch sowohl Speicher als auch CPU verschwendet werden. Siehe http://www.drdobbs.com/jvm/Java-qa/184404011

4
Tarik

Wenn Sie keinen Garbage Collector zum Komprimieren verwenden, kann aufgrund der Heap-Fragmentierung eine Art Speicherverlust auftreten.

4
user1050755

nvorsichtige Verwendung einer nicht statischen inneren Klasse in einer Klasse, die einen eigenen Lebenszyklus hat.

In Java enthalten nicht statische innere und anonyme Klassen einen impliziten Verweis auf ihre äußere Klasse. Statische innere Klassen dagegen nicht.

Hier ist ein häufiges Beispiel für einen Speicherverlust in Android, der jedoch nicht offensichtlich ist:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //.... 
      }
    }, SOME_TOME_TIME);

    // Go back to the previous Activity.
    finish();
  }}

Dadurch wird verhindert, dass der Aktivitätskontext überflüssig wird.

4
Jaskey

Ein Echtzeit-Beispiel für einen Speicherverlust vor JDK 1.7

angenommen, Sie lesen eine Datei mit 1000 Textzeilen und behalten das String-Objekt bei

String fileText = 1000 characters from file

fileText = fileText.subString(900, fileText.length());

Im obigen Code habe ich zunächst 1000 Zeichen gelesen und dann einen Teilstring ausgeführt, um nur die letzten 100 Zeichen zu erhalten. Jetzt sollte sich fileText nur auf 100 Zeichen beziehen, und alle anderen Zeichen sollten auf Müll gesammelt werden, da ich die Referenz verloren habe. Bevor die JDK 1.7-Teilzeichenfolgefunktion jedoch indirekt auf die ursprüngliche Zeichenfolge der letzten 100 Zeichen verweist und verhindert, dass die gesamte Zeichenfolge Müll sammelt und ganze 1000 Zeichen vorhanden sind im Speicher, bis Sie die Referenz der Teilzeichenfolge verlieren.

sie können ein Speicherverlust-Beispiel wie oben erstellen

0
Praveen Kumar

ein Speicherverlust ist eine Art von Ressourcenleck, das auftritt, wenn ein Computerprogramm die Speicherzuweisungen falsch verwaltet, sodass nicht mehr benötigter Speicher ) freigegeben wird => Wiki-Definition

Es ist eine Art relativ kontextbasiertes Thema, Sie können einfach ein Thema nach Ihrem Geschmack erstellen, solange die nicht verwendeten Referenzen nicht von Kunden verwendet werden und dennoch am Leben bleiben.

Das erste Beispiel sollte ein benutzerdefinierter Stack sein, ohne die veralteten Referenzen in effektiv Java Punkt 6 auf null zu setzen .

Natürlich gibt es noch viel mehr, so lange Sie wollen, aber wenn wir uns nur die integrierten Klassen von Java ansehen, könnte es so sein

subList()

Lassen Sie uns einen superdummen Code überprüfen, um das Leck zu erzeugen.

public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

Tatsächlich gibt es einen anderen Trick, den wir mit HashMap auslösen können, indem wir den Suchprozess ausnutzen. Es gibt eigentlich zwei Arten:

  • hashCode() ist immer gleich, aber equals() sind unterschiedlich;
  • benutze zufällige hashCode() und equals() immer true;

Warum?

hashCode() -> bucket => equals(), um das Paar zu lokalisieren


Ich wollte gerade substring() zuerst und dann subList() erwähnen, aber es scheint, dass dieses Problem bereits behoben ist, da seine Quelle in JDK 8 vorhanden ist.

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
0
Hearen