memory memory - Erstellen eines Speicherverlusts mit Java




leak example (25)

Statisches Feld, das Objektreferenz enthält [esp finales Feld]

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

Aufruf von String.intern() bei langem 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 geschlossene) 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 vom Speicherbereiniger der JVM nicht erreichbar sind , z. B. Speicher, der durch native Methoden zugewiesen wird

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

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

Falsche oder unangemessene JVM-Optionen , wie z. B. die Option noclassgc in IBM JDK, die die Sammlung nicht verwendeter Klassen verhindert

Siehe IBM jdk-Einstellungen .

Ich hatte gerade ein Interview und wurde gebeten, einen Speicherleck mit Java zu erstellen. Unnötig zu sagen, dass ich mich ziemlich dumm gefühlt habe und keine Ahnung hatte, wie ich überhaupt anfangen sollte.

Was wäre ein Beispiel?


Sie können ein Speicherleck für das Verschieben des Speichers erstellen, indem Sie in der finalize-Methode dieser Klasse eine neue Instanz einer Klasse erstellen. Bonuspunkte, wenn der Finalizer mehrere Instanzen erstellt. Hier ist ein einfaches Programm, das je nach Größe des Heaps zwischen einigen Sekunden und wenigen Minuten den gesamten Heap verlässt:

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

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 Referenzzählmüllsammlern. Sie würden dann höflich erklären, dass JVMs einen viel komplexeren Algorithmus verwenden, der diese Einschränkung nicht aufweist.

-Wes Tarle


Die Antwort hängt ganz von dem ab, was der Interviewer zu haben glaubte.

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

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

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

Ich lese Ihre Meta-Frage als "Welche Antwort hätte ich in dieser Interview-Situation haben können". Deshalb werde ich mich auf Interviewfähigkeiten anstatt auf Java konzentrieren. Ich glaube, Sie wiederholen wahrscheinlich häufiger die Situation, die Antwort auf eine Frage in einem Interview nicht zu kennen, als Sie sich an einem Ort befinden müssen, an dem Sie wissen müssen, wie Java-Fehler entstehen können. Das wird hoffentlich helfen.

Eine der wichtigsten Fähigkeiten, die Sie für ein Interview entwickeln können, besteht darin, zu lernen, aktiv auf die Fragen zuzuhören, und mit dem Interviewer zusammenzuarbeiten, um deren Absicht herauszufinden. Auf diese Weise können Sie nicht nur die Frage so beantworten, wie sie möchten, sondern auch, dass Sie über entscheidende Kommunikationsfähigkeiten verfügen. Und wenn es darum geht, zwischen vielen gleichermaßen talentierten Entwicklern zu wählen, werde ich denjenigen einstellen, der zuhört, denkt und versteht, bevor er jedes Mal reagiert.


Ich kann meine Antwort hier kopieren: Einfachste Möglichkeit, Speicherverlust in Java zu verursachen?

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

Die einfache Antwort lautet: Das geht nicht. Java führt eine automatische Speicherverwaltung durch und gibt Ressourcen frei, die für Sie nicht benötigt werden. Sie können das nicht aufhalten. Es wird IMMER möglich sein, die Ressourcen freizugeben. Bei Programmen mit manueller Speicherverwaltung ist das anders. Sie können mit malloc () etwas Speicher in C bekommen. Um den Speicher freizugeben, benötigen Sie den Zeiger, den Malloc zurückgegeben hat, und rufen Sie free () auf. Wenn Sie den Zeiger jedoch nicht mehr haben (überschrieben oder Lebensdauer überschritten), können Sie diesen Speicher leider nicht freigeben, und Sie haben einen Speicherverlust.

Alle anderen Antworten sind meiner Meinung nach keine wirklichen Speicherlecks. Sie alle zielen darauf ab, die Erinnerung schnell mit sinnlosen Sachen zu füllen. Sie können jedoch jederzeit die von Ihnen erstellten Objekte dereferenzieren und somit den Speicher freigeben -> NO LEAK. Die Antwort von acconrad kommt ziemlich nahe heran, da ich zugeben muss, da seine Lösung darin besteht, den Speicherbereiniger einfach "zu stürzen", indem er in einer Endlosschleife gezwungen wird).

Die lange Antwort lautet: Sie können einen Speicherverlust erhalten, indem Sie mithilfe der JNI eine Bibliothek für Java schreiben, die manuelle Speicherverwaltung und damit Speicherverluste haben kann. Wenn Sie diese Bibliothek aufrufen, verliert Ihr Java-Prozess Speicher. Oder Sie können Fehler in der JVM haben, sodass die JVM Speicher verliert. Es gibt wahrscheinlich Fehler in der JVM, vielleicht gibt es sogar einige bekannte, da die Speicherbereinigung nicht so banal ist, aber es ist trotzdem ein Fehler. Dies ist von Entwurf nicht möglich. Möglicherweise fragen Sie nach einem Java-Code, der durch einen solchen Fehler verursacht wird. Tut mir leid, ich kenne keinen und es könnte in der nächsten Java-Version sowieso kein Fehler mehr sein.


Mit sun.misc.Unsafe können Sie Speicherleck verursachen. 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 Reflection verwenden, um dies zu tun .

Code kann in Eclipse IDE nicht kompiliert werden - 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();
        }
    }

}

Nehmen Sie eine beliebige Webanwendung, die in einem Servlet-Container (Tomcat, Jetty, Glassfish oder was auch immer) läuft. Stellen Sie die App 10 oder 20 Mal hintereinander erneut bereit (es reicht möglicherweise aus, die WAR-Datei einfach im Autodeploy-Verzeichnis des Servers zu berühren.)

Wenn niemand dies tatsächlich getestet hat, sind die Chancen hoch, dass Sie nach einigen Umsetzungen einen OutOfMemoryError erhalten, da die Anwendung nicht darauf geachtet hat, sich selbst zu säubern. Sie können bei diesem Test sogar einen Fehler in Ihrem Server finden.

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 hat, Müll gesammelt werden können.

Wenn nur ein Verweis vorhanden ist, der die Deaktivierung Ihrer Web-App überlebt, können der entsprechende Classloader und folglich alle Klassen Ihrer Web-App nicht als Müll gesammelt werden.

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



So erstellen Sie ein echtes Speicherverlust (auf Objekte, auf die durch Ausführen von Code nicht zugegriffen werden kann, aber noch im Speicher gespeichert ist) in reinem Java:

  1. Die Anwendung erstellt einen lang laufenden Thread (oder verwenden Sie einen Thread-Pool, um noch schneller durchzusickern).
  2. Der Thread lädt eine Klasse über einen (optional benutzerdefinierten) ClassLoader.
  3. Die Klasse belegt einen großen Speicherbereich (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. Die Zuweisung des zusätzlichen Speichers ist optional (das Durchsickern der Klasseninstanz reicht aus), aber die Leckagen werden dadurch wesentlich schneller.
  4. Der Thread löscht alle Verweise auf die benutzerdefinierte Klasse oder den ClassLoader, aus dem sie geladen wurde.
  5. Wiederholen.

Dies funktioniert, weil das ThreadLocal einen Verweis auf das Objekt enthält, der einen Verweis auf seine Klasse enthält, der wiederum einen Verweis auf seinen ClassLoader behält. Der ClassLoader führt wiederum einen Verweis auf alle geladenen Klassen.

(In vielen JVM-Implementierungen, besonders vor Java 7, war das schlechter, da Classes und ClassLoaders direkt in permgen zugewiesen wurden und niemals gänzlich GC-abhängig waren. Unabhängig davon, wie die JVM die Klassenentladung behandelt, wird ein ThreadLocal jedoch immer noch verhindern Klassenobjekt wird nicht zurückgefordert.)

Eine Variation dieses Musters ist der Grund, 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 viele Leute ständig danach fragen, hier ein Beispielcode, der dieses Verhalten in Aktion zeigt .


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

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

Denken Sie daran, dass die Speicherzuordnungen im nativen Code vom JVM-Heap stammen.


Erstellen Sie eine statische Karte, und fügen Sie weitere feste Verweise hinzu. Die werden nie GC'd sein.

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

Der Interviewer suchte wahrscheinlich nach einem Zirkelverweis wie dem folgenden Code (der übrigens nur in sehr alten JVMs, die die Referenzzählung verwendeten, nur Speicher ausläuft, was nicht mehr der Fall ist). Dies ist jedoch eine ziemlich vage Frage, also eine hervorragende Gelegenheit, um Ihr Verständnis des JVM-Speichermanagements 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 der obige Code durch Referenzzählung Speicherplatz verlieren würde. Die meisten modernen JVMs verwenden jedoch keine Referenzzählung mehr, die meisten verwenden einen Sweep-Garbage-Collector, der diesen Speicher tatsächlich sammelt.

Als Nächstes können Sie erklären, wie Sie ein Objekt erstellen, das eine zugrunde liegende native Ressource hat:

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 ein Speicherverlust ist, aber in Wirklichkeit ist das ein Leck, da nativer Code in der JVM zugrunde liegende native Ressourcen zuordnet, die nicht durch Ihren Java-Code freigegeben wurden.

Am Ende des Tages müssen Sie mit einer modernen JVM etwas Java-Code schreiben, der eine native Ressource außerhalb des normalen Bereichs der JVM zur Verfügung stellt.


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

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

Einmal identifiziert, kann das Problem natürlich leicht gelöst werden.


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

Wenn diese fehlerhaften Schlüssel / Elemente hängen sollen, 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.

Die meisten Beispiele hier sind "zu komplex". Sie sind Randfälle. Bei diesen Beispielen hat der Programmierer einen Fehler gemacht (z. B. Gleichstellen / Hashcode nicht neu definieren) oder wurde von einem Eckfall der JVM / JAVA gebissen (Belastung der Klasse mit statischem Inhalt ...). Ich denke, das ist nicht die Art von Beispiel, die ein Interviewer haben möchte, oder sogar der häufigste Fall.

Es gibt jedoch wirklich einfachere Fälle für Speicherlecks. Der Garbage Collector gibt nur das frei, was nicht mehr referenziert wird. Wir als Java-Entwickler interessieren uns nicht für Speicher. Wir weisen es bei Bedarf zu und lassen es automatisch freigeben. Fein.

Aber jede langlebige Anwendung hat tendenziell einen gemeinsamen Status. Es kann alles sein, Statik, Singletons ... Häufig neigen nicht-triviale Anwendungen dazu, komplexe Objektdiagramme zu erstellen. Wenn Sie einfach vergessen, einen Verweis auf null zu setzen, oder öfter vergessen, ein Objekt aus einer Objektgruppe zu entfernen, genügt ein Speicherleck.

Natürlich neigen alle Arten von Listenern (wie UI-Listener), Caches oder alle langlebigen, gemeinsam genutzten Zustände zu Speicherverlusten, wenn sie nicht ordnungsgemäß verarbeitet werden. Zu verstehen ist, dass dies kein Java-Eckfall oder ein Problem mit dem Speicherbereiniger ist. Es ist ein Designproblem. Wir entwerfen, dass wir einem langlebigen Objekt einen Listener hinzufügen, den Listener jedoch nicht entfernen, wenn er nicht mehr benötigt wird. Wir cachen Objekte, aber wir haben keine Strategie, sie aus dem Cache zu entfernen.

Wir haben vielleicht ein komplexes Diagramm, das den vorherigen Zustand speichert, der für eine Berechnung erforderlich ist. Aber der vorherige Zustand ist an sich selbst gebunden und so weiter.

So müssen wir SQL-Verbindungen oder -Dateien schließen. Wir müssen geeignete Verweise auf null setzen und Elemente aus der Auflistung entfernen. Wir verfügen über geeignete Caching-Strategien (maximale Speichergröße, Anzahl der Elemente oder Timer). Alle Objekte, über die ein Listener benachrichtigt werden kann, müssen eine addListener- und eine removeListener-Methode bereitstellen. Und wenn diese Benachrichtigungen nicht mehr verwendet werden, müssen sie ihre Listenerliste löschen.

Ein Speicherleck ist in der Tat wirklich möglich und perfekt vorhersehbar. Keine besonderen Sprachfunktionen oder Eckfälle erforderlich. Speicherlecks sind entweder ein Hinweis darauf, dass möglicherweise etwas fehlt, oder sogar auf Designprobleme.


Threads werden nicht gesammelt, bis sie beendet sind. Sie dienen als roots der Müllsammlung. Sie sind eines der wenigen Objekte, die nicht einfach zurückgefordert werden können, indem man sie einfach vergisst oder Verweise auf sie löscht.

Beachten Sie: Das grundlegende Muster zum Beenden eines Worker-Threads besteht darin, eine Bedingungsvariable festzulegen, die der Thread sieht. Der Thread kann die Variable periodisch überprüfen und diese als Signal zum Abbruch verwenden. Wenn die Variable nicht deklariert ist volatile, kann der Thread die Änderung an der Variablen möglicherweise nicht sehen und weiß daher nicht, ob sie beendet werden soll. Oder stellen Sie sich vor, wenn einige Threads ein gemeinsam genutztes Objekt aktualisieren möchten, aber Deadlock beim Versuch, es zu sperren.

Wenn Sie nur eine Handvoll Threads haben, sind diese Fehler wahrscheinlich offensichtlich, da Ihr Programm nicht mehr ordnungsgemäß funktioniert. Wenn Sie einen Thread-Pool haben, der nach Bedarf weitere Threads erstellt, werden die veralteten / blockierten Threads möglicherweise nicht bemerkt und sammeln sich unbegrenzt an, wodurch ein Speicherverlust verursacht wird. Threads verwenden wahrscheinlich andere Daten in Ihrer Anwendung. Daher wird auch verhindert, dass alles, auf das sie direkt verweist, jemals erfasst wird.

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 System.gc()Sie alles an, was Sie mögen, aber das Objekt, an das Sie übergeben werden, leakMewird niemals sterben.

(* bearbeitet *)


Hier ist ein einfaches / unheimliches ü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 lange Sie einen StringLeaker im Spiel haben, haben Sie auch die gesamte Originalzeichenfolge im Speicher, auch wenn Sie denken, Sie halten nur eine Zeichenfolge mit einem Zeichen.

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 .intern()den Teilstring eingeben:

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

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


Ich fand es interessant, dass niemand die internen Klassenbeispiele verwendete. Wenn Sie eine interne Klasse haben; Es unterhält inhärent einen Verweis auf die enthaltende Klasse. Natürlich ist es technisch kein Speicherverlust, da Java es letztendlich aufräumt. Dies kann jedoch dazu führen, dass die Klassen länger als erwartet hängen bleiben.

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

Wenn Sie jetzt Example1 aufrufen und ein Beispiel2 erhalten, das Example1 verwirft, haben Sie an sich 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 Sie eine Variable haben, die länger als eine bestimmte Zeit existiert. Java geht davon aus, dass es immer existieren wird und es wird nie versucht, es aufzuräumen, wenn es nicht mehr im Code erreichbar ist. Das ist aber völlig unbestätigt.


Ich hatte einmal ein schönes "Speicherleck" in Bezug auf PermGen und XML-Parsing. Der von uns verwendete XML-Parser (ich kann mich nicht erinnern, welcher es war) hat ein String.intern () mit Tag-Namen durchgeführt, um den Vergleich zu beschleunigen. Einer unserer Kunden hatte die großartige Idee, Datenwerte nicht in XML-Attributen oder Text, sondern als Dateinamen zu speichern. Daher hatten wir ein Dokument wie:

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

Tatsächlich benutzten sie keine Nummern, sondern längere Text-IDs (etwa 20 Zeichen), die eindeutig waren und täglich 10-15 Millionen erreichten. Das ergibt 200 MB Müll pro Tag, der nie mehr benötigt wird und niemals gaschiert wird (da er sich in PermGen befindet). Wir hatten Permgen auf 512 MB gesetzt, so dass es ungefähr zwei Tage dauerte, bis die Out-of-Memory-Ausnahme (OOME) eintrifft ...


Im Folgenden wird ein nicht offensichtlicher Fall beschrieben, bei dem Java ausläuft, neben dem Standardfall vergessener Listener, statischer Verweise, gefälschter / veränderbarer Schlüssel in Hashmaps oder einfach nur Threads, die keine Chance haben, ihren Lebenszyklus zu beenden.

  • File.deleteOnExit() - File.deleteOnExit() immer die Zeichenfolge Wenn die Zeichenfolge eine Unterzeichenfolge ist, ist das Leck noch schlimmer (das darunterliegende Zeichen [] ist ebenfalls durchgesickert). - In Java 7 kopiert der Teilstring auch die char[] , so dass der spätere nicht gilt . @Daniel, aber keine Abstimmungen.

Ich werde mich auf Threads konzentrieren, um die Gefahr von nicht verwalteten Threads zu zeigen. Ich möchte nicht mal Swing anfassen.

  • Runtime.addShutdownHook und nicht entfernen ... und dann sogar mit removeShutdownHook aufgrund eines Fehlers in der ThreadGroup-Klasse in Bezug auf nicht gestartete Threads, dass sie möglicherweise nicht erfasst werden, wodurch die ThreadGroup wirksam wird. JGroup hat das Leck in GossipRouter.

  • Das Erstellen eines Thread , aber nicht das Starten, geht in die gleiche Kategorie wie oben.

  • Beim Erstellen eines Threads werden der ContextClassLoader und der AccessControlContext sowie die ThreadGroup und InheritedThreadLocal sowie alle diese Verweise potenzielle Lecks sowie die gesamten vom Classloader geladenen Klassen und alle statischen Verweise und Ja-Ja übernommen. Der Effekt ist vor allem im gesamten jucExecutor-Framework sichtbar, das über eine sehr einfache ThreadFactory Schnittstelle verfügt. Die meisten Entwickler haben jedoch keine Ahnung von der lauernden Gefahr. Auch viele Bibliotheken starten auf Anfrage Threads (viel zu viele branchenübliche Bibliotheken).

  • ThreadLocal Caches; das ist in vielen Fällen böse. Ich bin sicher, dass jeder recht einfache Caches auf ThreadLocal-Basis gesehen hat, auch die schlechte Nachricht: Wenn der Thread den Kontext ClassLoader mehr als erwartet weiterführt, ist dies ein nettes kleines Leck. Verwenden Sie ThreadLocal-Caches nur, wenn Sie wirklich benötigt werden.

  • Aufrufen von ThreadGroup.destroy() wenn die ThreadGroup selbst keine Threads hat, aber untergeordnete ThreadGroups behält. Ein schwerwiegendes Leck, das verhindert, dass die ThreadGroup von ihrem übergeordneten Element entfernt wird, aber alle untergeordneten Elemente werden nicht mehr aufgezählt.

  • Mit WeakHashMap und dem Wert (in) wird direkt auf den Schlüssel verwiesen. Ohne Heap-Dump ist dies schwer zu finden. Weak/SoftReference gilt für alle erweiterten Weak/SoftReference , die möglicherweise einen harten Verweis auf das überwachte Objekt enthalten.

  • Verwenden Sie java.net.URL mit dem HTTP (S) -Protokoll und laden Sie die Ressource von (!). Dies ist eine Besonderheit. Der KeepAliveCache erstellt einen neuen Thread in der System-ThreadGroup, der den Context-Klassenladeer des aktuellen Threads verliert. Der Thread wird bei der ersten Anforderung erstellt, wenn kein lebendiger Thread vorhanden ist, sodass Sie entweder Glück haben oder einfach nur durchsickern können. Das Leck ist in Java 7 bereits behoben, und der Code, mit dem der Thread erstellt wird, entfernt den Kontext-Classloader ordnungsgemäß. Es gibt wenige weitere Fälle ( wie ImageFetcher , auch behoben ) der Erstellung ähnlicher Threads.

  • Verwenden Sie InflaterInputStream indem Sie im Konstruktor (z. B. InflaterInputStream new java.util.zip.Inflater() und das PNGImageDecoder () nicht aufrufen. Nun, wenn Sie den Konstruktor einfach mit new , keine Chance ... Und ja, der Aufruf von close() für den Stream schließt den Inflater nicht, wenn er manuell als Konstruktorparameter übergeben wird. Dies ist kein echtes Leck, da es vom Finalizer freigegeben werden würde, wenn es notwendig erscheint. Bis zu diesem Moment frisst es nativen Speicher so sehr, dass Linux oom_killer den Prozess ungestraft beenden kann. Das Hauptproblem ist, dass die Finalisierung in Java sehr unzuverlässig ist und G1 bis 7.0.2 noch schlimmer wurde. Moral der Geschichte: Geben Sie native Ressourcen so schnell wie möglich frei; der Finalizer ist einfach zu schlecht.

  • Dasselbe gilt für java.util.zip.Deflater . Dies ist viel schlimmer, da der Deflater in Java speicherhungrig ist, dh er verwendet immer 15 Bits (max.) Und 8 Speicherebenen (9 ist max.) Und belegt mehrere hundert KB nativen Speichers. Zum Glück ist Deflater nicht weit verbreitet und JDK enthält meines Wissens keine Missbräuche. Rufen Sie immer end() wenn Sie einen Deflater oder Inflater manuell erstellen. Das Beste an den letzten beiden: Sie können sie nicht mit den üblichen Profilierungswerkzeugen finden.

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

Viel Glück und bleib in Sicherheit. Lecks sind böse!


Ich habe kürzlich eine Speicherverlust-Situation festgestellt, die log4j verursacht hat.

Log4j hat diesen Mechanismus namens Nested Diagnostic Context (NDC) , ein Instrument zur Unterscheidung verschachtelter Protokollausgabe von verschiedenen Quellen. Die Granularität, bei der NDC funktioniert, ist Threads. Es unterscheidet also Protokollausgaben von verschiedenen Threads getrennt.

Um Thread-spezifische Tags zu speichern, verwendet die NDC-Klasse von log4j eine Hashtable, die durch das Thread-Objekt selbst eingegeben wird (im Gegensatz zur Thread-ID). Bis das NDC-Tag im Speicher verbleibt, bleiben alle Objekte im Thread, die vom Thread hängen Objekt bleibt auch in Erinnerung. In unserer Webanwendung verwenden wir NDC, um Logoutputs 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 ebenfalls, während die Antwort von einer Anforderung zurückgegeben wird. Das Problem trat auf, wenn während der Verarbeitung einer Anforderung ein untergeordneter Thread erzeugt wurde, der in etwa wie der folgende Code aussieht:

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 dem 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 Objekt riesigeList hängt. Selbst nachdem der Thread seine Arbeit beendet hatte, wurde der Verweis auf die riesige Liste durch den NDC-Kontext Hastable am Leben erhalten, wodurch ein Speicherverlust verursacht wurde.


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


Wie viele Leute vorgeschlagen haben, sind Ressourcenlecks relativ leicht zu verursachen - wie in den JDBC-Beispielen. Die tatsächlichen Speicherverluste sind etwas schwieriger - vor allem, wenn Sie sich nicht auf beschädigte JVM-Teile verlassen, um dies für Sie zu erledigen ...

Die Idee, Objekte zu erstellen, die einen sehr großen Footprint haben und dann nicht auf sie zugreifen können, ist auch kein echter Speicherverlust. Wenn nichts darauf zugreifen kann, wird Müll aufgesammelt, und wenn etwas darauf zugreifen kann, ist es kein Leck.

Eine Methode, die früher funktioniert hat - und ich weiß nicht, ob sie es immer noch tut - besteht darin, eine kreisförmige Kette mit drei Tiefen zu haben. Da Objekt A einen Verweis auf Objekt B hat, hat Objekt B einen Verweis auf Objekt C und Objekt C einen Verweis auf Objekt A. Der GC war klug genug zu wissen, dass eine zwei tiefe Kette - wie in A <-> B - kann sicher abgeholt werden, wenn A und B für nichts anderes zugänglich sind, die Dreiwegekette jedoch nicht handhaben können ...


Was ist ein Speicherleck:

  • Es ist auf einen Fehler oder ein schlechtes Design zurückzuführen.
  • Es ist eine Verschwendung von Erinnerung.
  • Im Laufe der Zeit wird es schlimmer.
  • Der Müllsammler kann es nicht reinigen.

Typisches Beispiel:

Ein Cache von 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 eingesaugt. Ein besseres Design verwendet eine LRUMap (behält nur zuletzt verwendete Objekte im Cache).

Natürlich können Sie die Dinge noch komplizierter gestalten:

  • Verwenden von ThreadLocal- Konstruktionen.
  • Hinzufügen komplexerer Referenzbäume .
  • oder Lecks durch Bibliotheken von Drittanbietern .

Was passiert oft:

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


Benutzen:

Random ran = new Random();
int x = ran.nextInt(6) + 5;

Die ganze Zahl x ist jetzt die Zufallszahl, die einen möglichen Ausgang von 5-10 .







java memory memory-leaks