web-dev-qa-db-de.com

Kann es einen Speicherverlust in Java geben?

Ich bekomme diese Frage oft gestellt. Was ist ein guter Weg, um zu antworten

33
javaguy

Kann es in Java einen Speicherverlust geben?

Die Antwort ist, dass es von der Art des Speicherverlusts abhängt, von dem Sie sprechen.

Klassische C/C++ - Speicherverluste treten auf, wenn eine Anwendung ein Objekt free oder dispose vernachlässigt, wenn sie damit fertig sind, und es tritt ein Leck auf. Zyklische Referenzen sind ein Teilfall davon, bei dem die Anwendung Schwierigkeiten hat, zu wissen, wann free/dispose zu verwenden ist, und dies versäumt, dies zu tun. Verwandte Probleme sind, wenn die Anwendung ein Objekt verwendet, nachdem es freigegeben wurde, oder versucht, es zweimal freizugeben. (Sie könnten die letzteren Probleme als Speicherlecks oder einfach als Bugs bezeichnen. So oder so ...)

Java und andere (voll1) verwaltete Sprachen meistens leiden nicht unter diesen Problemen, da der GC dafür sorgt, dass Objekte freigegeben werden, die nicht mehr erreichbar sind. (Auf jeden Fall gibt es keine Probleme mit baumelnden Zeigern und doppelten Freiheitsgraden, und Zyklen sind nicht problematisch, wie dies bei C/C++ - "intelligenten Zeigern" und anderen Referenzzählschemata der Fall ist.)

In einigen Fällen fehlen jedoch Objekte in GC in Java, die (aus Sicht des Programmierers) müllsammelbar sein sollten. Dies geschieht, wenn der GC nicht feststellen kann, dass ein Objekt nicht erreicht werden kann:

  • Die Logik/der Zustand des Programms kann so sein, dass die Ausführungspfade, die eine Variable verwenden würden, nicht auftreten können. Der Entwickler kann dies als offensichtlich ansehen, aber der GC kann nicht sicher sein und ist vorsichtig (wie erforderlich).
  • Der Programmierer könnte sich irren, und der GC vermeidet, was sonst zu einer baumelnden Referenz führen könnte.

(Beachten Sie, dass die Ursachen für Speicherverluste in Java einfach oder sehr subtil sein können; siehe die Antwort von @ jonathan.cone für einige subtile. Die letzte betrifft möglicherweise externe Ressourcen, auf die Sie nicht vertrauen sollten GC sowieso zu behandeln.)

In beiden Fällen kann es vorkommen, dass unerwünschte Objekte nicht mit dem Hausmüll entsorgt werden können und Speicherplatz nicht mehr verfügbar ist. Dies ist ein Speicherverlust.

Dann besteht das Problem, dass eine Java-Anwendung oder -Bibliothek Off-Heap-Objekte über systemeigenen Code zuordnen kann, die manuell verwaltet werden müssen. Wenn die Anwendung/Bibliothek fehlerhaft ist oder nicht ordnungsgemäß verwendet wird, kann ein systemeigener Speicherverlust auftreten. (Zum Beispiel: Android-Bitmap-Speicherverlust ... mit dem Hinweis, dass dieses Problem in späteren Versionen von Android behoben ist.)


1 - Ich spiele auf ein paar Dinge an. In einigen verwalteten Sprachen können Sie nicht verwalteten Code schreiben, in dem Sie klassische Speicherlecks erstellen können. Einige andere verwaltete Sprachen (oder genauer gesagt Sprachimplementierungen) verwenden eher die Referenzzählung als die ordnungsgemäße Speicherbereinigung. Ein auf Referenzzählern basierender Speichermanager benötigt etwas (d. H. Die Anwendung), um die Zyklen zu unterbrechen. Andernfalls kommt es zu Speicherlecks.

21
Stephen C

Wenn man bedenkt, dass Java einen Garbage Collector verwendet, um nicht verwendete Objekte zu sammeln, kann man keinen baumelnden Zeiger haben. Sie können jedoch ein Objekt länger als nötig im Gültigkeitsbereich belassen, was als Speicherverlust angesehen werden kann. Mehr dazu hier: http://web.archive.org/web/20120722095536/http://www.ibm.com:80/developerworks/rational/library/05/0816_GuptaPalanki/

Machst du einen Test dazu oder so? Denn genau dort ist das mindestens ein A +.

10
AnthonyJoeseph

Ja. Speicherverluste können auch bei einem GC auftreten. Sie können sich beispielsweise an Ressourcen wie Datenbankergebnismengen halten, die Sie manuell schließen müssen.

8
Markus Johnsson

Die Antwort ist ein klares ja , aber dies ist im Allgemeinen ein Ergebnis des Programmiermodell und kein Hinweis auf einen Fehler in der JVM. Dies ist häufig der Fall, wenn Frameworks andere Lebenszyklen aufweisen als eine laufende JVM. Einige Beispiele sind:

  1. Kontext neu laden
  2. Versäumnis, Beobachter (Zuhörer) dereferenzieren
  3. Vergessen, Ressourcen zu bereinigen, nachdem Sie sie verwendet haben *

* - Milliarden von Beratungsdollar wurden für die Lösung des letzten Problems aufgewendet

5
jonathan.cone

Ja, in dem Sinne, dass Ihre Java-Anwendung im Laufe der Zeit Speicher ansammeln kann, den der Garbage Collector nicht freigeben kann.

Durch die Beibehaltung von Verweisen auf nicht benötigte/unerwünschte Objekte werden sie niemals außer Reichweite geraten und ihr Gedächtnis wird nicht zurückgefordert.

4
Tyler

ja, wenn Sie die Referenzierung von Objekten nicht aufheben, werden sie niemals mit dem Müll gesammelt, und die Speichernutzung nimmt zu. Aufgrund der Art und Weise, wie Java entworfen wurde, ist dies jedoch schwierig zu erreichen, während es in einigen anderen Sprachen manchmal schwierig ist, dies nicht zu erreichen.

bearbeiten: Lies Amokranes Link. das ist gut.

3
pstanton

Das Buch Effective Java gibt zwei weitere Gründe für "Memory Leaks":

  • Sobald Sie den Objektverweis in Cache abgelegt haben, vergessen Sie, dass er dort ist. Die Referenz bleibt lange im Cache, bevor sie irrelevant wird. Die Lösung besteht darin, den Cache als WeakHashMap darzustellen.
  • in einer API, in der Clients Rückrufe registrieren und diese nicht explizit neu registrieren. Die Lösung besteht darin, nur schwache Referenzen zu speichern.
2
Trivikram

Die kurze Antwort:

Eine kompetente JVM weist keine Speicherverluste auf, es kann jedoch mehr Speicher verwendet werden, als benötigt wird, da noch nicht alle nicht verwendeten Objekte durch Garbage Collected erfasst wurden. Außerdem können Java-Apps selbst Verweise auf Objekte enthalten, die sie nicht mehr benötigen, und dies kann zu einem Speicherverlust führen.

Eine noch kürzere Antwort:

Java ist eine Sprache und kann keine Speicherverluste aufweisen, nur Java Virtual Machines können dies.

2

Ja, es ist möglich.

In Effective Java gibt es ein Beispiel, bei dem ein Stapel mithilfe von Arrays implementiert wird. Wenn Ihre Pop-Operationen einfach den Indexwert dekrementieren, kann es zu einem Speicherverlust kommen. Warum? Denn Ihr Array hat immer noch einen Verweis auf den gepoppten Wert und Sie haben immer noch einen Verweis auf das Stapelobjekt. Das Richtige für diese Stack-Implementierung wäre, den Verweis auf den gepoppten Wert mit einer expliziten Null-Zuweisung am gepoppten Array-Index zu löschen.

2
dteoh

Ja, dies kann in einem Kontext der Fall sein, in dem ein Programm fälschlicherweise einen Verweis auf ein Objekt enthält, das nie wieder verwendet werden würde und daher vom GC nicht bereinigt wird.

Ein Beispiel dafür wäre, zu vergessen, einen geöffneten Stream zu schließen:

class MemoryLeak {

    private void startLeaking() throws IOException {
        StringBuilder input = new StringBuilder();
        URLConnection conn = new URL("www.example.com/file.txt").openConnection();

        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));        

        while (br.readLine() != null) {
            input.append(br.readLine());
        }
    }

    public static void main(String[] args) throws IOException {
        MemoryLeak ml = new MemoryLeak();
        ml.startLeaking();
    }
}
1
Stas

Eine einfache Antwort lautet: JVM kümmert sich um die gesamte Initialisierung von POJOs [einfachen alten Java-Objekten], solange Sie nicht mit JNI arbeiten. Wenn Sie mit JNI eine Speicherzuordnung mit dem nativen Code vorgenommen haben, müssen Sie sich selbst um diesen Speicher kümmern.

0
Swaranga Sarma

Ja. Ein Speicherverlust ist nicht verwendeter Speicher, der von der App nicht an den Speichermanager freigegeben wurde.

Ich habe schon oft Java-Code gesehen, der Elemente in einer Datenstruktur speichert, aber die Elemente werden nie von dort entfernt und füllen den Speicher bis zu einem OutOfMemoryError:

void f() {
    List<Integer> w = new ArrayList<Integer>();
    while (true) {
         w.add(new Integer(42));
    }
}

Obwohl dieses Beispiel zu offensichtlich ist, sind Java-Speicherfehler in der Regel subtiler. Verwenden Sie beispielsweise Dependency Injection, um ein großes Objekt in einer Komponente mit SESSION scope zu speichern, ohne es freizugeben, wenn das Objekt nicht mehr verwendet wird.

Bei einer 64-Bit-Version von VM wird dies tendenziell schlimmer, da der Swap-Speicherplatz so lange gefüllt wird, bis das System zu viele IO Vorgänge durchforstet.

0
vz0