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.