2012-11-23 19 views
12

Myślę, że mam wyciek pamięci w moim Android tapety na żywo. Za każdym razem, gdy obracam ekran, ilość zebranych śmieci pamięci rośnie o 50 kb i nie spada. Myślę, że może to być spowodowane zaplanowaną przyszłością, więc przedstawię scenariusz, aby zobaczyć, czy tak jest.Czy zaplanowana przyszłość może spowodować wyciek pamięci?

Załóżmy, że masz klasę (nazwijmy ją Foo), która ma następujących członków.

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

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

A teraz ustawić zaplanowaną przyszłość

future = scheduler.scheduleAtFixedRate(runnable, delay, speed, 
       TimeUnit.MILLISECONDS); 

Przyszłość posiada odniesienie do runnable i runnable posiada odniesienie do dominującej Foo obiektu. Nie jestem pewien, czy tak jest, ale czy ten fakt może oznaczać, że jeśli nic w programie nie zawiera wzmianki o Foo, śmieciarz nadal nie może go odebrać, ponieważ jest zaplanowana przyszłość? Nie jestem zbyt dobry w wielowątkowości, więc nie wiem, czy kod, który pokazałem, oznacza, że ​​zaplanowane zadanie będzie żyło dłużej niż obiekt, co oznacza, że ​​nie będzie to śmieci.

Jeśli ten scenariusz nie spowoduje, że Foo nie zostanie usunięte, wystarczy, że otrzymam proste wyjaśnienie. Jeśli to uniemożliwi Foo ze zbierania śmieci, to jak mogę to naprawić? Czy musisz zrobić future.cancel(true); future = null;? Czy część future = null jest niepotrzebna?

Odpowiedz

5
  • Każda metoda run opiera się na otaczającą Foo klasy i dlatego nie może żyć samodzielnie. W takim przypadku nie widzę sposobu, w jaki mógłbyś mieć swój komputer i utrzymywać swój "żywy" program do uruchomienia przez wykonawcę, lub twój metode jest statyczny w tym sensie, że nie zależy od stanu z klasy Foo, w takim przypadku możesz uczynić ją statyczną i zapobiegnie występującemu problemowi.

Nie wydaje się, aby poradzić sobie z przerwaniem w swoim Runnable. Oznacza to, że nawet jeśli zadzwonisz pod numer future.cancel(true), Twój Runnable będzie nadal działał, co, jak ustaliłeś, może być przyczyną twojego wycieku.

Istnieje kilka sposobów, aby uczynić runnable "przyjaznym przerwaniu". Albo wywołasz metodę, która generuje wyjątek InterruptedException (np. Thread.sleep() lub blokującą metodę IO), a następnie wyrzuci InterruptedException, gdy przyszłość zostanie anulowana. Można złapać ten wyjątek i wyjść metodę run niezwłocznie po czyszczone co należy oczyścić i przywrócenie stanu przerwał

public void run() { 
    while(true) { 
     try { 
      someOperationThatCanBeInterrupted(); 
     } catch (InterruptedException e) { 
      cleanup(); //close files, network connections etc. 
      Thread.currentThread().interrupt(); //restore interrupted status 
     } 
    } 
}  

Jeśli nie wywołać żadnych takich metod, a średnia idiom jest:

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

W takim przypadku należy upewnić się, że stan w czasie jest regularnie sprawdzany.

Z tymi zmianami, po wywołaniu future.cancel(true), sygnał przerwania zostanie wysłany do wątku wykonującego twój Runnable, który zakończy działanie, czyniąc twój Runnable i twoją instancję Foo uprawnioną do GC.

+0

Moja metoda uruchamiania opiera się na zamkniętej klasie Foo. Ale kiedy wcześniej zniszczę odniesienie do obiektu Foo, co powinienem zrobić, aby powstrzymać działanie, które może spowodować wyciek pamięci. Czy 'future.cancel (true)' będzie wystarczające? Robię to już i przeciek pamięci wciąż tam jest. Oczywiście może to oznaczać, że runnable nie jest źródłem wycieku. – gsingh2011

+0

@ gsingh2011 czy jesteś pewien, że 'future.cancel (true)' anuluje twoją przyszłość? Innymi słowy, czy twoja praca może zostać przerwana i czy przerwie ona pracę, kiedy zostanie przerwana? – assylias

+0

Ponieważ nie rozumiem, co oznacza "można przerwać działanie", zakładam, że nie można go przerwać. Jak mam to zrobić? – gsingh2011

0

Przyszłość posiada odniesienie do runnable i runnable posiada odniesienie do dominującej Foo obiektu.Nie jestem pewien, czy tak jest, , ale czy ten fakt może oznaczać, że jeśli nic w programie nie zawiera odwołania do Foo o numerze , nadal nie można go odzyskać, ponieważ jest zaplanowana przyszłość?

Jest to zły pomysł, aby Foo jakiś przemijający obiektu utworzenie często dlatego należy zamykaniu systemu ScheduledExecutorService scheduler gdy aplikacja zamyka. Dlatego powinieneś uczynić Foo pseudonimem.

Odśmierzacz rozumie cykle, więc po wyłączeniu usługi executorów Foo najprawdopodobniej nie będziesz mieć problemów z pamięcią.

5

Mimo, że na to pytanie odpowiedź jest długa, ale po przeczytaniu artykułu this pomyślałem o zamieszczeniu nowej odpowiedzi z wyjaśnieniem.

Czy zaplanowana przyszłość może spowodować wyciek pamięci? --- TAK

ScheduledFuture.cancel() lub Future.cancel() w ogóle nie powiadomi jej Executor że został odwołany i pozostaje w kolejce, aż jej czas na wykonanie przyjechał. To nie jest wielka sprawa dla prostych kontraktów futures, ale może być dużym problemem dla ScheduledFutures. Może pozostać tam przez kilka sekund, minut, godzin, dni, tygodni, lat lub prawie bezterminowo w zależności od opóźnień, w których został zaplanowany.

Oto przykład z najgorszym scenariuszem. Działające i wszystko, do czego się odwołuje, pozostanie w Kolejce przez długie. MAX_VALUE milisekund, nawet po tym, jak jego Przyszłość została anulowana!

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

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

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

    future.cancel(true); 
} 

Widać to przy użyciu Profiler lub wywołanie metody ScheduledThreadPoolExecutor.shutdownNow() który zwróci listę z jednym elementem w nim (to jest Runnable że odwołano).

Rozwiązaniem tego problemu jest napisanie własnej przyszłej implementacji lub wywołanie metody purge() co jakiś czas. W przypadku niestandardowego rozwiązania fabryki Executora:

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; 
} 
+1

Czy mógłbyś wyjaśnić, co stanie się w tym scenariuszu? Jest to POJEDYNCZY wykonawca z gwintem. Jeśli zadanie Runnable jest długim zadaniem (powiedzmy dzień), a opóźnienie i okres są takie same jak powyżej. Czy zadanie zostanie umieszczone w kolejce? Czy istnieje górny limit kolejki zadań? – cherryhitech