Automatyczne zarządzanie pamięcią poprzez zbieranie śmieci to kluczowy mechanizm platformy Android, oparty na maszynie wirtualnej ART. Według Google Android Documentation, 2026, garbage collector uwalnia programistę od ręcznego zarządzania pamięcią, automatycznie usuwając obiekty, do których nie ma już referencji. Bez GC każde przydzielenie obiektu wymagałoby jawnego wywołania free lub delete, co w ekosystemie Java z milionami obiektów na sekundę jest fizycznie niemożliwe.
Najważniejsze
Garbage Collection (GC) — to automatyczny proces wykrywania i zwalniania pamięci zajętej przez obiekty, które nie są już używane przez program. W kontekście tworzenia aplikacji mobilnych GC jest stosowany na platformie Android poprzez maszynę wirtualną ART, a także w standardowej Java Virtual Machine.
W przeciwieństwie do języków z ręcznym zarządzaniem pamięcią (C, C++), gdzie programista musi jawnie wywoływać free lub delete, GC całkowicie przejmuje zadanie śledzenia cyklu życia obiektów. Programista tworzy nowe obiekty przez operator new, a garbage collector określa moment, w którym obiekt staje się nieosiągalny — to znaczy, że nie pozostała do niego żadna aktywna referencja.
Główna metryka efektywności GC — czas pauzy (pause time) i przepustowość (throughput). Pauza to odcinek, na który wstrzymywane jest wykonywanie aplikacji w celu przeprowadzenia zbierania. W środowisku mobilnym pauzy dłuższe niż 8–16 milisekund są zauważalne jako pominięte klatki (jank).
Według danych Google I/O 2019, ART w Android 10 skrócił typowe pauzy GC do 2–4 ms, co jest o 70% mniej w porównaniu z Dalvik w Android 4.4. Niemniej jednak, nieprawidłowa praca z pamięcią — częsta alokacja obiektów w pętlach, tworzenie tymczasowych instancji bez potrzeby — pozostaje główną przyczyną problemów z wydajnością.
Wszystkie implementacje GC w Javie i Androidzie opierają się na kilku fundamentalnych algorytmach, które są łączone w celu osiągnięcia równowagi między czasem pauzy a kompletnością czyszczenia. Zrozumienie tych algorytmów jest niezbędne do pisania GC-friendly kodu.
Mark-and-Sweep — najprostszy algorytm działający w dwóch etapach. W fazie Mark garbage collector przechodzi przez graf obiektów, zaczynając od korzeni (root set) — zmiennych lokalnych, pól statycznych, stosów wątków. Każdy osiągalny obiekt jest oznaczany flagą live. W fazie Sweep garbage collector przechodzi przez całą stertę i zwalnia pamięć nieoznaczonych obiektów.
Wadą jest fragmentacja pamięci: po Sweep wolne obszary przeplatają się z zajętymi, co utrudnia przydzielanie dużych obiektów. W scenariuszach mobilnych jest to krytyczne, ponieważ sterta jest zwykle mała (64–512 MB na Androidzie).
Copying Collection dzieli stertę na dwie półprzestrzenie (semi-spaces). Aktywne obiekty są kopiowane z jednej półprzestrzeni do drugiej w sposób zwarty, bez przerw. Po skopiowaniu stara półprzestrzeń jest całkowicie uznawana za wolną. Algorytm całkowicie eliminuje fragmentację, ale wymaga dwa razy więcej pamięci.
W środowiskach mobilnych Copying Collection jest stosowany przez garbage collectory generacyjne do szybkiego czyszczenia młodych obiektów, które statystycznie umierają wcześnie (hipoteza słabego pokolenia).
Generational Collection dzieli stertę na pokolenia: Young Generation (młode obiekty) i Old Generation (stare, które przetrwały kilka kolekcji). Zbieranie młodego pokolenia (Minor GC) jest wykonywane często i szybko, ponieważ większość obiektów umiera młodymi. Zbieranie starego pokolenia (Major GC lub Full GC) zdarza się rzadziej, ale trwa dłużej.
// Demonstracja generacyjnego GC: młode obiekty umierają szybko
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // żyje przez całą metodę
for (Item item : items) {
Result r = new Result(item.getValue()); // umiera natychmiast
if (r.isValid()) {
process(r); // r staje się śmieciem
}
}
saveResults(results); // results przechodzi do Old Gen
}
W tym przykładzie obiekty Result są tworzone wewnątrz pętli i natychmiast stają się śmieciem — są idealnymi kandydatami dla Young GC. Obiekt results żyje dłużej i migruje do Old Generation. Podział na pokolenia pozwala Minor GC czyścić młode obiekty w milisekundach, nie dotykając starej sterty.
Android przeszedł drogę od Dalvik VM do ART (Android Runtime), a implementacja GC jest jedną z kluczowych różnic między nimi. Zrozumienie architektury GC w Androidzie pomaga pisać kod, który minimalizuje pauzy na rzeczywistych urządzeniach.
| Cecha | Dalvik (do 4.4) | ART (5.0+) |
|---|---|---|
| Typ GC | Mark-and-Sweep z Concurrent Mark | Generacyjny + Concurrent |
| Typowa pauza | 10–30 ms | 2–4 ms |
| Kompaktyzacja | Nie (tylko fragmentacja rośnie) | Tak (w tle, bez zatrzymywania aplikacji) |
| Kompilacja AOT | JIT (Just-In-Time) | AOT + JIT (hybrydowa) |
Dalvik używał kombinacji Mark-and-Sweep z fazą concurrent. Concurrent Mark pozwalał aplikacji kontynuować pracę podczas przechodzenia grafu obiektów, ale faza Sweep wymagała zatrzymania wszystkich wątków (Stop-The-World). Na urządzeniach z małą ilością RAM (512 MB — 1 GB) pauzy osiągały 30 ms, co powodowało zauważalne przycięcia interfejsu. Ponadto Dalvik nie kompaktyzował sterty, dlatego po dłuższej pracy rosła fragmentacja, a przydzielanie dużych obiektów (np. Bitmap) mogło wyrzucić OutOfMemoryError przy wystarczającej całkowitej ilości wolnej pamięci.
ART (Android Runtime) wprowadził generacyjny garbage collector z kompaktyzacją współbieżną. Sterta jest dzielona na trzy regiony: Young, Mature (odpowiednik Old Generation) i Large Object Space (dla obiektów większych niż 12 KB). Zbieranie regionu Young odbywa się równolegle bez zatrzymywania wątków w większości przypadków. W Android 10+ pojawiła się Concurrent Copying — kompaktyzacja wykonywana w wątku tła bez Stop-The-World.
Dzięki architekturze ART, typowe pauzy GC zmniejszyły się do 2–4 ms, a w scenariuszach z przewagą młodych obiektów — do 0.5–1 ms. Pozwoliło to urządzeniom Android zapewniać stabilne 60 FPS nawet przy aktywnej pracy z pamięcią.
W ekosystemie Java istnieje kilka implementacji GC, każda z własnym profilem wydajności. Dla tworzenia aplikacji na Android wybór jest ograniczony do ART, ale znajomość Java GC jest przydatna przy pisaniu części serwerowej aplikacji mobilnych oraz przy tworzeniu aplikacji w Kotlin Multiplatform.
Serial GC — jednowątkowy garbage collector z całkowitym zatrzymaniem aplikacji (Stop-The-World). Każda operacja Mark, Sweep i Compact jest wykonywana przez jeden wątek. Wydajność jest niska — nie jest stosowany w serwerach mobilnych. Nadaje się tylko do małych aplikacji ze stertą do 100 MB.
Parallel GC (znany również jako Throughput Collector) używa wielu wątków dla wszystkich faz zbierania. Jest zorientowany na maksymalną przepustowość (throughput) — minimalizuje czas poświęcony na GC względem czasu działania aplikacji. Włączany przez flagę -XX:+UseParallelGC w JVM.
G1 (Garbage-First) GC — domyślny garbage collector w Java 9+. Sterta jest dzielona na regiony po 1–32 MB. G1 przewiduje czas pauzy i dąży do zmieszczenia się w zadanym limicie (domyślnie 200 ms). Priorytet: najpierw czyszczone są regiony z największą ilością śmieci (stąd nazwa). G1 jest efektywny dla serwerów z dużą stertą (4–64 GB) i przewidywalnymi pauzami.
// Włączenie G1 GC z docelowym czasem pauzy 100 ms
// java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar
public class MemoryMonitor {
private static final long THRESHOLD = 512 * 1024 * 1024; // 512 MB
public void checkHeapUsage() {
Runtime rt = Runtime.getRuntime();
long used = rt.totalMemory() - rt.freeMemory();
if (used > THRESHOLD) {
System.out.println("Przekroczenie progu użycia sterty: " + used);
System.out.println("Rozważ zmniejszenie alokacji");
}
}
}
Monitorowanie sterty przez Runtime pozwala wykrywać wycieki pamięci na wczesnym etapie. Jeśli used przekracza 80% maksymalnej sterty przy stabilnej pracy — to sygnał o możliwym wycieku lub nadmiernym zużyciu pamięci przez aplikację.
Nawet nowoczesny ART GC nie rozwiązuje wszystkich problemów — nieprawidłowe używanie pamięci pozostaje główną przyczyną jank i ANR (Application Not Responding). Przyjrzyjmy się głównym scenariuszom i metodom optymalizacji.
GC Pauses — zatrzymania wątków aplikacji na czas zbierania. Na ekranie objawia się to jako pominięte klatki, gdy czas między dwiema klatkami przekracza 16.6 ms (60 FPS). Jeśli GC trwa 30 ms, rysowana jest tylko jedna klatka zamiast dwóch — użytkownik widzi przycięcia interfejsu.
Główne przyczyny długich pauz: duża liczba żywych obiektów w Old Generation, fragmentacja sterty, częste Full GC. Do diagnostyki używa się Android Studio Profiler i systrace.
Główna zasada GC-friendly kodu — minimalizować liczbę alokowanych obiektów. Każdy nowy obiekt wymaga nie tylko przydzielenia pamięci, ale także późniejszego zebrania. Nawet jeśli GC jest szybki, 1000 zbędnych alokacji na sekundę daje 1000 sprawdzeń dla garbage collectora.
Wyciek pamięci występuje, gdy obiekt pozostaje osiągalny, mimo że nie jest już potrzebny. GC nie może usunąć takiego obiektu, a pamięć stopniowo się wyczerpuje. Typowe przyczyny: nieodpięte listenery, statyczne referencje do Activity, anonimowe klasy przechwytujące zewnętrzny kontekst oraz niezamknięte Cursor/InputStream.
// Wyciek pamięci: anonimowa klasa przechowuje referencję do Activity
public void startTask() {
new Thread(new Runnable() { // niejawnie przechowuje this (Activity)
@Override
public void run() {
// długa operacja...
System.out.println("Gotowe");
}
}).start();
}
// Poprawka: statyczna klasa zagnieżdżona + WeakReference
private static class TaskRunnable implements Runnable {
private WeakReference<Activity> activityRef;
TaskRunnable(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void run() {
Activity act = activityRef.get();
if (act != null) {
// bezpieczna praca z Activity
}
}
}
W tym przykładzie anonimowy Runnable przechwytuje niejawną referencję do Activity. Dopóki wątek żyje — Activity nie może być zebrane przez GC, nawet jeśli użytkownik już zamknął ekran. Poprawka WeakReference + static class przerywa ten łańcuch i pozwala na utylizację Activity.
Często zadawane pytania
GC w Androidzie (ART) to generacyjny garbage collector z kompaktyzacją współbieżną, zoptymalizowany dla urządzeń mobilnych z ograniczoną pamięcią. Java GC (G1, ZGC) to serwerowe garbage collectory z dużymi stertami i przewidywalnymi pauzami. ART GC nie używa flag JVM — cała konfiguracja jest wykonywana automatycznie na poziomie systemu operacyjnego.
Stop-The-World — moment, w którym garbage collector wstrzymuje wszystkie wątki aplikacji, aby bezpiecznie przejść przez graf obiektów lub zwolnić pamięć. Im dłuższy STW, tym bardziej zauważalny jest jank. ART skrócił typowy czas STW do 2–4 ms dzięki architekturze generacyjnej.
Użyj Android Studio Memory Profiler — pokazuje wzrost sterty, liczbę alokacji i umożliwia wykonanie Heap Dump. Do głębokiej analizy używaj LeakCanary — biblioteka automatycznie wykrywa wycieki i pokazuje łańcuch referencji uniemożliwiających zbieranie GC.
Full GC — pełne zbieranie wszystkich pokoleń sterty, w tym Old Generation. W aplikacjach mobilnych Full GC może trwać 50–200 ms, powodując zauważalny jank lub ANR. Główne przyczyny: fragmentacja sterty, wycieki pamięci, przekroczenie progu Old Generation.
Kotlin zapewnia korutyny ze strukturalną współbieżnością — anulowanie zakresu automatycznie anuluje wszystkie korutyny potomne, zapobiegając wyciekom. Ponadto w Kotlinie jest delegat lazy do leniwej inicjalizacji oraz operatory zakresu widzialności, które zmniejszają liczbę tymczasowych obiektów.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również