3 Stimmen

Java-Speicherrätsel

Angenommen, ich habe folgenden Code

package memoryleak;

public class MemoryLeak {

    public static int size;

    static {
        size = (int) (Runtime.getRuntime().maxMemory()*0.6);
    }

    public static void main(String[] args) throws InterruptedException {
        {
            byte[] data1 = new byte[size];
        }

        byte[] data2 = new byte[size];
    }
}

Dieser Code erzeugt OutOfMemoryError. Sie können diesen Code mit einer Variablenzuweisung zum Laufen bringen (die den vom ersten Array verwendeten Stack-Frame umschreibt und das Array für Garbage Collecting verfügbar macht). Dieses Rätsel erklärt aquí .

{
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

Die Frage ist: Warum funktioniert der folgende Code immer noch nicht?

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

Und folgende Arbeiten:

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
synchronized (o) {
    byte[] data2 = new byte[size];
}

6voto

Tom Hawtin - tackline Punkte 142461

Meine Wette ist, dass synchronized fügt ein Element in den Rahmen ein, wodurch data1 um einen Platz nach oben zu kommen und nicht von i . synchronized muss dasselbe Objekt, das es gesperrt hat, wieder entsperren, auch wenn sich der Ort/das Feld ändert.

El synchronized Der Code würde etwa so aussehen:

Object $sync = o;
$sync.lock();
try {
    byte[] data1 = new byte[size];
} finally {
    $sync.unlock();
}

Nehmen wir also das letzte Codebeispiel:

Object o = new Object();            // Slot 0.
synchronized (o) {                  // Slot 1.
    byte[] data1 = new byte[size];  // Slot 2.
}                                 
int i = 0;                          // Slot 1.
synchronized (o) {                  // Slot 2. (clobbers data1, was slot 1)
    byte[] data2 = new byte[size];  // Slot 3.
}

0voto

Thilo Punkte 248982

Puzzles sind interessant, aber für den pragmatischen Programmierer, der nicht über die geheimnisvollen Aspekte der Garbage Collection nachdenken will (oder, was noch wichtiger ist, davon abhängig ist), wäre die Einstellung data1 = null das Problem lösen, sobald es nicht mehr benötigt wird? Wenn ja, würde ich lieber tun, dass dann seltsam synchronisierten Block und Dummy-Variable Magie.

Natürlich ist es traurig, dass der Speicher nicht freigegeben wird, sobald das Array den Gültigkeitsbereich verlässt, was man sich von der dieses Thema .

Dies sollte in der JVM behoben werden.

0voto

cygil Punkte 3514

All dieses Verhalten ist abhängig von der Implementierung. Der Garbage Collector läuft in seinem eigenen asynchronen Thread, der nichts mit dem Synchronisationsverhalten Ihres Programms zu tun hat. Sie wissen einfach nicht, wann das Array, auf das data1 verweist, vom Garbage Collector entsorgt wird - Sie können nur hoffen, dass dies in einer "vernünftigen" Zeitspanne geschieht, nachdem es aus dem Anwendungsbereich verschwunden ist bzw. alle Verweise auf das Array gelöscht wurden.

Wenn Sie sich Sorgen machen, dass Ihnen der Speicher in Ihrem Programm ausgeht, können Sie explizit versuchen, mit System.gc() einen Garbage-Collection-Zyklus auszulösen. Aber auch dies garantiert nicht, dass genügend Speicher verfügbar ist, wenn Sie Daten2 zuweisen. Der Aufruf von System.gc() ist lediglich ein Hinweis an die Laufzeitumgebung, dass Sie jetzt einen Garbage-Collection-Zyklus durchführen lassen möchten.

In Java ist die Zuweisung und Freigabe von Speicher nicht-deterministisch. Der Garbage Collector wird ausgeführt, wenn er läuft, und man kann ihn nicht auf Programmebene steuern. Es gibt keine relevanten Unterschiede zwischen den von Ihnen geposteten Codeschnipseln, da das Verhalten des Garbage Collectors nicht-deterministisch ist und der genaue Zeitpunkt, zu dem er ausgelöst wird, von der Implementierung und dem System abhängt. Manchmal ist dies ein Problem für Ihre Anwendung - zum Beispiel wenn es sich um ein Betriebssystem oder ein eingebettetes Gerät mit begrenztem Speicher handelt - und Sie müssen in C++ oder einer anderen Sprache programmieren, in der die Speicherverwaltung deterministisch ist. Die meisten von uns vertrauen jedoch einfach darauf, dass der Garbage Collector sich einigermaßen vernünftig verhält, und das ist für die meisten Zwecke gut genug - obwohl man, wie Sie sehen, auch künstlichen Code erstellen kann, der Probleme verursacht.

Update: Peinlich. Wie mich ein paar andere Kommentatoren daran erinnert haben, wird ein Garbage Collection-Zyklus explizit ausgelöst, bevor der JVM einen OutOfMemory-Fehler auslöst. Allerdings ist das Verhalten immer noch nicht-deterministisch: als dieser Link erklärt, garantiert der JVM nicht, dass alle toten Objekte in einem Garbage Collection-Zyklus entdeckt werden.

-1voto

Spence Punkte 27536

Sie verlassen sich darauf, dass der GC vor Ihrer Instanziierung sammelt?

könnten Sie nicht

Object o = new Object();
byte[] data1 = new byte[size];
GC.Collect()
byte[] data2 = new byte[size];

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X