W Javie zarządzanie pamięcią odbywa się dzięki automatycznej zbiórce śmieci (Garbage Collector, GC). JVM samodzielnie śledzi obiekty w pamięci: jeśli obiekt nie ma już więcej odniesień, staje się niedostępny i może zostać usunięty.
GC składa się z różnych faz, takich jak Mark, Sweep i czasami Compact. W fazie Mark obiekty, do których można dotrzeć z korzeni (GC roots), są oznaczane jako żywe. Następnie rozpoczyna się Sweep: nieużywane obiekty są usuwane. Czasami wykonywane jest Compact — defragmentacja pamięci.
Istnieją różne typy GC: Serial, Parallel, CMS, G1. Należy je dobierać w zależności od typu obciążenia aplikacji.
List<byte[]> dataList = new ArrayList<>(); while(true) { dataList.add(new byte[1024*1024]); // Obiekty pozostają dostępne }
W tym przykładzie obiekty nigdy nie staną się niedostępne, GC ich nie usunie, co spowoduje OutOfMemoryError.
Jeśli obiekt nie ma już żadnych odniesień, kiedy gwarantowane jest wywołanie jego finalizatora (finalize())?
Odpowiedź: Wywołanie metody finalize() wcale nie jest gwarantowane! Może być wywołane nigdy lub z opóźnieniem. Nie można polegać na finalize() w celu zwolnienia zasobów.
@Override protected void finalize() throws Throwable { // Może nie zadziałać! }
Historia
W dużym systemie e-commerce programiści mieli nadzieję zautomatyzować czyszczenie plików tymczasowych za pomocą metody finalize(), ale z powodu rzadkiego wywoływania tej metody pamięć podręczna została przepełniona plikami tymczasowymi, co doprowadziło do braku miejsca na dysku i przestojów.
Historia
W high-load REST API przypadkowo zapisano wewnętrzną pamięć podręczną odniesień do dużych obiektów, co uniemożliwiło zwolnienie pamięci. Po awarii aplikacji z OutOfMemoryError analiza wykazała błąd w metodzie pamięci podręcznej.
Historia
Finalizatory były używane do zwalniania połączeń sieciowych, uważając to za "pewny sposób". Prowadziło to do sytuacji, w której połączenia utknęły w systemie na nieokreślony czas, powodując blokady i wyczerpanie puli połączeń.