Zarządzanie pamięcią

0

Witam. Mam pytanie odnośnie zarządzania pamięcią w Javie i nieszczęsnego Garbage collector'a. Oto przykładowy kod:


class Player
{
   private String data;
   public Player()
   {
      data = new String();
      data = "przykładowy tekst";
   }

}

class Glowna
{
    public Glowna(){}
          ActionListener actionListener = new ActionListener() {
          public void actionPerformed(ActionEvent actionEvent) {
            Player temp = new Player();      
          }
        }
  //oczywiscie w kodzie jest również funkcja main i button z podpietym listenerem
}

 

Teraz klikając sobie za każdym razem w JButtona tworze nowy obiekt. Przyzwyczajony do c++ byłem przekonany, że po opuszczeniu funkcji actionPerformed obiekt temp przestanie istnieć. Niestety tak się nie dzieje. Sprawdzałem to różnymi sposobami. Zużycie pamięci nie spada, a funkcja finalize() klasy Player nie jest wywoływana. Co mógłbym zrobić by móc zwolnić pamięć. Użycie temp = null; również nie pomaga.

Z góry dziękuję za pomoc
Hilar

0

W Javie jak i .NOT, Pythonie, Ruby, PHP, Haskellu, itp itd są mechanizmy odśmiecania, które co jakiś tam czas szukają nieosiągalnych obiektów i zwalniają zajmowane przez nich miejsce. To "co jakiś czas" jest niedeterministyczne, przynajmniej w większości maszyn wirtualnych.

Sprawdź sobie taki kod:


public class Main3  {

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("dupa");
    }

    public static void main(String[] args) {
        while (true) {
            new Main3();
        }
    }
}

Powinien rzucać dupami na potęgę.

0

W Javie jest leniwe odśmiecanie, więc pamięć jest masowo zwalniana dopiero kiedy jest potrzeba przydzielania dużych porcji danych (wtedy takie new, które uruchomiło lawinę może działać wolno). Weź też pod uwagę, że jeżeli obiekt Player miesza coś z wątkami (np. tworzy nowy wątek i trzyma jego referencję), to nie zostanie zwolniony dopóki wszystkie wątki się nie zakończą. Można więc w ten sposób doprowadzić do "wycieku pamięci". Szczególnie jak pojawią się odwołania cykliczne. Będzie to z punktu widzenia śmieciarza trochę podobne do zakleszczenia.
Generalnie umieszczanie wywołań new w obsłudze zdarzeń (która jest na wątku EDT) nie jest w Javie najlepszym pomysłem. Powinno się w niej raczej zmieniać stan aplikacji, który spowoduje, że osobny wątek (np. główny) sam wykona zlecone zadanie, czyli utworzy potrzebne obiekty. Najlepiej aby obsługa zdarzeń modyfikowała odpowiednio stan gui, wrzucała zadania do wykonania (inne niż modyfikacja kontrolek) do jakiejś kolejki i natychmiast się kończyła.

Dodatkowo jeżeli pisałeś w C++, to może jeszcze nie wiesz, że to co w Javie jest oznaczone jako "free memory" nie jest tym co w C++. W C++ wolna pamięć sterty, to ta którą system w danej chwili oferuje plus cała pamięć wcześniej otrzymana, a obecnie wolna (np. po starych obiektach). Natomiast w Javie wolna pamięć nie obejmuje tej, którą system w danej chwili oferuje (dlatego wolna pamięć Javy jest bardzo mała). W odróżnieniu od C++ zarządca pamięci JVM nigdy nie oddaje pamięci z powrotem do systemu, więc zajęta pamięć dla całej JVM (jej instancji) z punktu widzenia systemu może się tylko powiększać. Oddawana jest dopiero po skillowaniu JVM (np. po System.exit()).

Może ten kod lepiej to wyjaśni:

/**
 * Klasa informacyjna dla pamięci JVM.
 * Przykładowe użycie: JavaMemory.get().free()
 * Interfejs klasy możliwy do rozszerzania.
 * @author Olamagato
 */
abstract public class JavaMemory
{
	/**
	 * Uzyskuje jedyny istniejący obiekt JavaMemory.
	 * @return obiekt JavaMemory (singleton)
	 */
	public static JavaMemory get()
		{ return jm != null? jm : (jm = new JavaMemory(){}); }

	/**
	 * Maksymalna ilość pamięci jaką może uzyskać biężąca JVM.
	 * @return ilość bajtów pamięci
	 */
	public long max() { return maxMemory; }

	/**
	 * Aktualna ilość pamięci używana przez bieżącą JVM.
	 * @return ilość bajtów pamięci
	 */
	public long vm() { return rtm.totalMemory(); }

	/**
	 * Ilość pamięci używana przez wszystkie aplikacje bieżącej instancji JVM.
	 * Korzysta z polimorficznej metody JavaMemory.vm.
	 * @return ilość bajtów pamięci
	 */
	public long used() { return vm() - rtm.freeMemory(); }

	/**
	 * Wolna ilość pamięci dostępna dla aplikacji w chwili wywołania.
	 * Korzysta z polimorficznej metody JavaMemory.used.
	 * @return ilość bajtów pamięci
	 */
	public long free() { return maxMemory - used(); }

	private JavaMemory() {}

	private static final Runtime rtm = Runtime.getRuntime();
	private static JavaMemory jm;

	/** cache singletona */
	private final long maxMemory = rtm.maxMemory();
}

Krótko mówiąc wolna pamięć w sensie C++, to maxMemory - totalMemory + freeMemory z uwzględnieniem, że maxMemory jest ustalona arbitralnie do wielkości oferowanej przez system pamięci wirtualnej, a nie jak w C++ jest to cała pamięć wirtualna jaką system dysponuje (fizyczna + swapy). W przypadku kodu 32-bit maxMemory to teoretycznie cała przestrzeń adresowa (do 2/3/4 GB), jednak w praktyce ponieważ przydzielany obszar musi być adresowo ciągły, to rzadko przekracza 1,5 GB. W czystym C++ wymóg ciągłości adresowania nie jest potrzebny.

1 użytkowników online, w tym zalogowanych: 0, gości: 1