leaks - java memory leak troubleshooting




Erstellen eines Speicherverlusts mit Java (20)

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

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

Das Problem mit dem oben genannten ist, dass das Connection Objekt nicht geschlossen wird und die physische Verbindung daher offen bleibt, bis der Garbage Collector vorbeikommt und feststellt, dass er nicht erreichbar ist. GC ruft die finalize Methode auf, es gibt jedoch JDBC-Treiber, die das finalize nicht implementieren, zumindest nicht auf dieselbe Weise wie Connection.close . Das Ergebnis ist, dass zwar Speicher aufgrund von nicht erreichbaren Objekten wiedererlangt wird, Ressourcen (einschließlich Speicher), die dem Connection Objekt zugeordnet sind, möglicherweise einfach nicht wieder freigegeben werden.

In einem solchen Fall, in dem die finalize Methode von Connection nicht alles aufräumt, könnte es tatsächlich vorkommen, dass die physische Verbindung zum Datenbankserver mehrere Garbage Collection-Zyklen dauert, bis der Datenbankserver schließlich feststellt, dass die Verbindung nicht aktiv ist ( Wenn ja, und sollte geschlossen werden.

Selbst wenn der JDBC-Treiber finalize implementieren sollte, können während der Finalisierung Ausnahmen ausgelöst werden. Das Ergebnis ist, dass jeder Speicher, der mit dem jetzt "ruhenden" Objekt verknüpft ist, nicht zurückgefordert wird, da das finalize garantiert nur einmal aufgerufen wird.

Das obige Szenario, bei dem während des Objektabschlusses Ausnahmen auftreten, bezieht sich auf ein anderes Szenario, das möglicherweise zu einem Speicherleck führen kann - Objektwiederbelebung. Die Wiederbelebung von Objekten wird häufig absichtlich durchgeführt, indem ein starker Verweis auf das Objekt erstellt wird, der von einem anderen Objekt abgeschlossen wird. Wenn Objektwiederbelebung missbraucht wird, führt dies zu Speicherverlusten in Kombination mit anderen Quellen für Speicherverluste.

Es gibt noch viele weitere Beispiele, die Sie sich vorstellen können

  • Verwalten einer Listeninstanz, bei der Sie nur zur Liste hinzufügen und nicht aus ihr löschen (obwohl Sie Elemente entfernen sollten, die Sie nicht mehr benötigen) oder
  • Socket s oder File s File , aber nicht schließen, wenn sie nicht mehr benötigt werden (ähnlich dem obigen Beispiel mit der Connection Klasse).
  • Singletons werden nicht entfernt, 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 die Singleton-Instanz wird daher niemals gesammelt. Wenn eine neue Instanz der Anwendung implementiert wird, wird normalerweise ein neuer Klassenlader erstellt, und der vorherige Klassenlader bleibt aufgrund des Singleton weiterhin bestehen.

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?


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.


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.

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

}

Wahrscheinlich eines der einfachsten Beispiele für einen potenziellen Speicherverlust und wie er vermieden werden kann, 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 implementieren würden, hätten Sie gedacht, das nicht mehr verwendete Array-Element zu löschen ( elementData[--size] = null )? Diese Referenz könnte ein riesiges Objekt am Leben erhalten ...


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 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.


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.


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).


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.


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

Es gibt viele verschiedene Situationen, in denen der Speicher ausläuft. Einer, dem ich begegnet bin, der eine Karte enthüllt, die nicht an anderer Stelle sichtbar gemacht 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 {

}

Ich bin kürzlich auf eine subtilere Art von Ressourcenleck gestoßen. Wir öffnen Ressourcen über den Klassenlader getResourceAsStream, und die Eingabestream-Handles wurden nicht geschlossen.

Du könntest sagen, was für ein Idiot.

Nun, das macht das interessant: Auf diese Weise können Sie Speicherbündel des zugrundeliegenden Prozesses und nicht von JVMs Speicherhaufen verlieren.

Sie benötigen lediglich eine JAR-Datei mit einer darin enthaltenen Datei, auf die aus Java-Code verwiesen wird. Je größer die JAR-Datei ist, desto schneller wird der Speicher zugewiesen.

Sie können ein solches Glas leicht 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 einfach eine Datei mit dem Namen BigJarCreator.java ein, kompilieren Sie sie und führen Sie sie über die Befehlszeile aus:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: In Ihrem aktuellen Arbeitsverzeichnis finden Sie ein Jar-Archiv mit zwei 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, erstellt jedoch nicht referenzierte InputStream-Objekte. Diese Objekte werden sofort Müll gesammelt und tragen daher nicht zur Heapgröße 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 sich nicht sicher sind, versuchen Sie, die Klasse oben zu kompilieren und zu starten, aber wählen Sie eine anständige Größe des Heapspeichers (2 MB):

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

Hier wird kein OOM-Fehler angezeigt, da keine Referenzen beibehalten werden. Die Anwendung wird weiter ausgeführt, unabhängig von der Größe, die Sie im obigen Beispiel für ITERATIONS ausgewählt haben. Der Speicherverbrauch Ihres Prozesses (oben sichtbar (RES / RSS) oder Prozess-Explorer) steigt, wenn die Anwendung nicht auf den Befehl wait kommt. Im obigen Setup werden etwa 150 MB Speicherplatz zugewiesen.

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

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

Ihr Prozess überschreitet nicht 35 MB, unabhängig von der Anzahl der Iterationen.

Ganz einfach und überraschend.


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 ...


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.


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

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 *)





memory-leaks