15 Stimmen

Kann ein geplanter zukünftiger Zeitpunkt zu einem Speicherleck führen?

Ich denke, in meinem Android-Live-Hintergrund gibt es ein Memory-Leck. Immer wenn ich den Bildschirm drehe, erhöht sich die Menge an gesammeltem Speicherabfall um 50 KB und geht nicht wieder zurück. Ich denke, das könnte durch eine geplante Zukunft verursacht werden, also werde ich ein Szenario präsentieren, um zu sehen, ob das der Fall ist.

Angenommen, Sie haben eine Klasse (nennen wir sie Foo), die die folgenden Elemente hat.

private ScheduledFuture future;
private final ScheduledExecutorService scheduler = Executors
        .newSingleThreadScheduledExecutor();

private final Runnable runnable = new Runnable() {
    public void run() {
        // Do stuff
    }
};

Und jetzt setzen Sie eine geplante Zukunft

future = scheduler.scheduleAtFixedRate(runnable, Verzögerung, Geschwindigkeit,
                ZeitEinheiten.MILLISECONDS);

Die Zukunft hält eine Referenz auf das ausführbare Objekt, und das ausführbare Objekt hält eine Referenz auf das übergeordnete Foo-Objekt. Ich bin mir nicht sicher, ob das der Fall ist, aber könnte diese Tatsache bedeuten, dass, wenn nichts im Programm eine Referenz auf Foo hält, der Garbage-Collector es immer noch nicht sammeln kann, weil es eine geplante Zukunft gibt? Ich bin nicht besonders gut im Multithreading, also weiß ich nicht, ob der von mir gezeigte Code bedeutet, dass die geplante Aufgabe länger leben wird als das Objekt, was bedeutet, dass es nicht als Müll gesammelt wird.

Wenn dieses Szenario nicht dazu führt, dass Foo nicht vom Garbage Collector gesammelt wird, muss mir das nur mit einer einfachen Erklärung gesagt werden. Wenn es verhindert, dass Foo gesammelt wird, wie kann ich das beheben? Muss ich Zukunft.cancel(true); Zukunft = null; machen? Ist der Teil Zukunft = null unnötig?

7voto

Vipin Punkte 4403

Auch wenn diese Frage bereits lange beantwortet wurde, dachte ich nach dem Lesen dieses Artikels darüber nach, eine neue Antwort mit Erklärung zu veröffentlichen.

Kann ein geplanter Future einen Memory-Leak verursachen? --- JA

ScheduledFuture.cancel() oder Future.cancel() informiert im Allgemeinen nicht den Executor, dass er abgebrochen wurde, und bleibt in der Warteschlange, bis seine Ausführungszeit erreicht ist. Das ist kein großes Problem für einfache Futures, kann aber ein großes Problem für ScheduledFutures sein. Es kann dort Sekunden, Minuten, Stunden, Tage, Wochen, Jahre oder fast unbegrenzt verweilen, je nach den Verzögerungen, mit denen es geplant wurde.

Hier ist ein Beispiel mit dem schlimmsten Fall Szenario. Der ausführbare Code und alles, worauf er verweist, bleiben selbst nach der Stornierung in der Warteschlange für Long.MAX_VALUE Millisekunden!

public static void main(String[] args) {
    ScheduledThreadPoolExecutor executor 
        = new ScheduledThreadPoolExecutor(1);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hallo Welt!");
        }
    };

    ScheduledFuture future 
        = executor.schedule(task, 
            Long.MAX_VALUE, TimeUnit.MILLISECONDS);

    future.cancel(true);
}

Sie können dies durch Verwendung eines Profiler oder durch Aufruf der Methode ScheduledThreadPoolExecutor.shutdownNow() sehen, die eine Liste mit einem Element zurückgibt (es handelt sich um den Runnable, der abgebrochen wurde).

Die Lösung für dieses Problem ist entweder die Implementierung Ihres eigenen Future oder das regelmäßige Aufrufen der purge()-Methode. Im Falle eines benutzerdefinierten Executor-Factory-Lösung ist:

public static ScheduledThreadPoolExecutor createSingleScheduledExecutor() {
    final ScheduledThreadPoolExecutor executor 
        = new ScheduledThreadPoolExecutor(1);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            executor.purge();
        }
    };

    executor.scheduleWithFixedDelay(task, 30L, 30L, TimeUnit.SECONDS);

    return executor;
}

6voto

assylias Punkte 308529
  • Entweder hängt Ihre run-Methode von der umgebenden Foo-Klasse ab und kann daher nicht unabhängig existieren. In diesem Fall sehe ich nicht, wie Sie Ihre Foo für GC freigeben könnten und Ihren ausführbaren "lebendig" halten, um vom Executor ausgeführt zu werden
  • oder Ihre run-Methode ist statisch in dem Sinne, dass sie nicht vom Zustand Ihrer Foo-Klasse abhängt, in diesem Fall könnten Sie sie statisch machen und sie wird das Problem verhindern, das Sie erleben.

Sie scheinen keine Unterbrechung in Ihrem Runnable zu behandeln. Das bedeutet, dass auch wenn Sie future.cancel(true) aufrufen, Ihr Runnable weiterhin ausgeführt wird, was wie Sie festgestellt haben, eine Ursache für Ihr Leak sein könnte.

Es gibt mehrere Möglichkeiten, ein Runnable "unterbrechungsfreundlich" zu machen. Entweder rufen Sie eine Methode auf, die InterruptedException wirft (wie Thread.sleep() oder eine blockierende IO-Methode) und sie wird eine InterruptedException werfen, wenn die Zukunft abgebrochen wird. Sie können diese Ausnahme abfangen und die run-Methode prompt verlassen, nachdem Sie aufgeräumt und den unterbrochenen Zustand wiederhergestellt haben:

public void run() {
    while(true) {
        try {
            someOperationThatCanBeInterrupted();
        } catch (InterruptedException e) {
            cleanup(); //Dateien schließen, Netzwerkverbindungen etc.
            Thread.currentThread().interrupt(); //unterbrochenen Status wiederherstellen
        }
    }
}    

Wenn Sie solche Methoden nicht aufrufen, ist das Standard-Idiom:

public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        doYourStuff();
    }
    cleanup();
}

In diesem Fall sollten Sie sicherstellen, dass die Bedingung im while regelmäßig überprüft wird.

Mit diesen Änderungen wird beim Aufruf von future.cancel(true) ein Unterbrechungssignal an den Thread gesendet, der Ihr Runnable ausführt, der dann das, was er tut, beendet, sodass Ihr Runnable und Ihre Foo-Instanz für den GC berechtigt sind.

0voto

Adam Gent Punkte 45977

Die Zukunft hält einen Verweis auf das ausführbare Objekt, und das ausführbare Objekt hält einen Verweis auf das übergeordnete Foo-Objekt. Ich bin mir nicht sicher, ob dies der Fall ist, aber könnte diese Tatsache bedeuten, dass selbst wenn nichts im Programm auf Foo verweist, der Garbage Collector es dennoch nicht sammeln kann, weil es eine geplante Zukunft gibt?

Es ist keine gute Idee, Foo eine Art temporäres Objekt zu erstellen, das oft erstellt wird, da Sie den ScheduledExecutorService-Scheduler schließen sollten, wenn Ihre App geschlossen wird. Daher sollten Sie Foo zu einem Pseudo-Singleton machen.

Der Garbage Collector versteht Zyklen, also nachdem Sie den Executor-Service von Foo heruntergefahren haben, werden Sie höchstwahrscheinlich keine Speicherprobleme haben.

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