زباله‌روبی (GC): این چیست، الگوریتم‌ها و جمع‌آوری زباله در توسعه موبایل

نویسنده: IT Sectr منتشر شده: 2026-03-29 زمان مطالعه: 10 دقیقه

مدیریت خودکار حافظه از طریق زباله‌روبی مکانیسم کلیدی پلتفرم Android است که بر ماشین مجازی ART استوار دارد. به گزارش Google Android Documentation, 2026، زباله‌روب توسعه‌دهنده را از مدیریت دستی حافظه آزاد می‌کند و شیئ‌هایی را که دیگر ارجاعی ندارند به صورت خودکار حذف می‌کند. بدون GC هر تخصیص شیئ نیازمند فراخوانی صریح free یا delete بود که در اکوسیستم Java با میلیون‌ها شیئ در ثانیه از لحاظ فیزیکی غیرممکن است.

نکات کلیدی

  • Garbage Collection — مکانیسم آزادسازی خودکار حافظه با حذف شیئ‌های استفاده‌نشده در Java و Android
  • الگوریتم‌های پایه — Mark-and-Sweep، Copying Collection و Generational Collection کارایی جمع‌آوری را تعیین می‌کنند
  • ART و Dalvik — دو پیاده‌سازی ماشین مجازی Android، جایی که ART (Android Runtime) جایگزین Dalvik از Android 5.0 شد
  • GC Pauses — توقف اجرای اپلیکیشن در طول جمع‌آوری — علت اصلی jank و مشکلات کارایی
  • بهینه‌سازی GC — کاهش تخصیص‌ها، استفاده از استخرهای شیئ و انتخاب درست انواع Collection بار زباله‌روب را کاهش می‌دهد

Garbage Collection (GC) چیست؟

Garbage Collection (GC) — یک فرآیند خودکار برای کشف و آزادسازی حافظه‌ای است که توسط شیئ‌هایی اشغال شده که دیگر توسط برنامه استفاده نمی‌شوند. در زمینه توسعه موبایل، GC در پلتفرم Android از طریق ماشین مجازی ART و همچنین در Java Virtual Machine استاندارد اعمال می‌شود.

بر خلاف زبان‌های با مدیریت دستی حافظه (C, C++) که برنامه‌نویس مجبور است صریحاً free یا delete را فراخوان کند، GC کاملاً وظیفه پیگیری چرخه عمر شیئ‌ها را بر عهده می‌گیرد. توسعه‌دهنده شیئ‌های جدید را از طریق عامل new ایجاد می‌کند و زباله‌روب لحظه‌ای را که شیئ غیرقابل دسترس می‌شود را تعیین می‌کند — یعنی هیچ ارجاع فعالی به آن باقی نمانده است.

اصلی‌ترین متریک کارایی GC — زمان مکث (pause time) و انتقال دهی (throughput). مکث مدتی است که اجرای اپلیکیشن برای انجام جمع‌آوری متوقف می‌شود. در محیط موبایل، مکاث بیشتر از 8–16 میلی‌ثانیه به صورت فرم‌های افتاده (jank) قابل توجه هستند.

استناد به Google I/O 2019، ART در Android 10 مکاث معمولی GC را به 2–4 ms کاهش داد که نسبت به Dalvik در Android 4.4 ۷۰ درصد کمتر است. با این حال، کار نادرست با حافظه — تخصیص مکرر شیئ در حلقه‌ها، ایجاد نمونه‌های موقت بدون نیاز — علت اصلی مشکلات کارایی باقی می‌ماند.

زباله‌روب چگونه کار می‌کند: الگوریتم‌های پایه

همه پیاده‌سازی‌های GC در Java و Android بر چند الگوریتم بنیادین استوار هستند که برای دستیابی به تعادل بین زمان مکث و کاملیت پاکسازی ترکیب می‌شوند. درک این الگوریتم‌ها برای نوشتن کد مناسب برای GC ضروری است.

Mark-and-Sweep

Mark-and-Sweep — ساده‌ترین الگوریتم که در دو مرحله کار می‌کند. در مرحله Mark، زباله‌روب گراف شیئ‌ها را از ارجاعات ریشه (root set) — متغیرهای محلی، فیلدهای استاتیک، پیشکهای پرونده‌ها — پیمایش می‌کند. هر شیئ قابل دسترس با علمت live علامت‌گذاری می‌شود. در مرحله Sweep، زباله‌روب از کل پیشکنبه عبور می‌کند و حافظه شیئ‌های علامت‌گذاری نشده را آزاد می‌کند.

نقص — تکه تکه شدن حافظه: پس از Sweep، بخش‌های آزاد با بخش‌های اشغالی درهم قرار می‌گیرند که تخصیص شیئ‌های بزرگ را دچار مشکل می‌کند. در سناریوهای موبایل این حریجانی است چون پیشکنبه معمولاً کوچک است (64–512 MB در Android).

Copying Collection

Copying Collection پیشکنبه را به دو نیم‌فضا (semi-spaces) تقسیم می‌کند. شیئ‌های فعال از یک نیم‌فضا به دیگری به صورت متراکم و بدون شکاف کپی می‌شوند. پس از کپی، نیم‌فضای قدیمی کاملاً آزاد اعلام می‌شود. این الگوریتم کاملاً تکه تکه شدن را برطرف می‌کند، اما دو برابر حافظه نیاز دارد.

در محیط‌های موبایل، Copying Collection توسط زباله‌روب‌های نسلی برای پاکسازی سریع شیئ‌های جوان استفاده می‌شود که به صورت آماری زود می‌میرند (فرضیه نسل ضعیف).

Generational Collection

Generational Collection پیشکنبه را به نسل‌ها تقسیم می‌کند: Young Generation (شیئ‌های جوان) و Old Generation (شیئ‌های پیر که چند بار جمع‌آوری را تحمل کرده‌اند). جمع‌آوری نسل جوان (Minor GC) به صورت مکرر و سریع انجام می‌شود، چون اکثر شیئ‌ها جوان می‌میرند. جمع‌آوری نسل پیر (Major GC یا Full GC) به ندرت اتفاق می‌افتد اما بیشتر طول می‌کشد.

java
// نمایش GC نسلی: شیئ‌های جوان زود می‌میرند
void processItems(List<Item> items) {
    List<Result> results = new ArrayList<>();       // تمام متد زندگی می‌کند
    for (Item item : items) {
        Result r = new Result(item.getValue());    // فوراً می‌میرد
        if (r.isValid()) {
            process(r);                               // r زباله می‌شود
        }
    }
    saveResults(results);                             // results به Old Gen منتقل می‌شود
}

در این مثال، شیئ‌های Result درون حلقه ایجاد می‌شوند و بلافاصله زباله می‌شوند — آنها کاندیدای ایده‌ای برای Young GC هستند. شیئ results بیشتر زندگی می‌کند و به Old Generation مهاجرت می‌کند. جداسازی نسل‌ها به Minor GC اجازه می‌دهد شیئ‌های جوان را در چند میلی‌ثانیه پاک کند بدون اینکه به پیشکنبه قدیمی دست بزند.

Garbage Collection در Android: ART و Dalvik

Android راهی از Dalvik VM به ART (Android Runtime) طی کرده است و پیاده‌سازی GC یکی از تفاوت‌های کلیدی بین آنهاست. درک معماری GC در Android به نوشتن کدی کمک می‌کند که مکاث را در دستگاه‌های واقعی به حداقل برساند.

ویژگیDalvik (تا 4.4)ART (5.0+)
نوع GCMark-and-Sweep با Concurrent Markنسلی + Concurrent
مکث معمولی10–30 ms2–4 ms
تراکمندارد (تکه تکه شدن افزایش می‌یابد)دارد (در پس‌زمینه، بدون توقف اپلیکیشن)
کامپایل AOTJIT (Just-In-Time)AOT + JIT (ترکیبی)

Dalvik GC

Dalvik از ترکیب Mark-and-Sweep با فاز concurrent استفاده می‌کرد. Concurrent Mark به اپلیکیشن اجازه می‌داد در طول پیمایش گراف شیئ‌ها به کار خود ادامه دهد، اما مرحله Sweep نیازمند توقف همه پرونده‌ها (Stop-The-World) بود. در دستگاه‌های با حافظه کم (512 MB — 1 GB)، مکاث به 30 ms می‌رسید که باعث کندی قابل توجه در رابط می‌شد. علاوه بر این، Dalvik پیشکنبه را متراکم نمی‌کرد، بنابراین پس از کار طولانی، تکه تکه شدن افزایش می‌یافت و تخصیص شیئ‌های بزرگ (مانند Bitmap) می‌توانست OutOfMemoryError را حتی با حافظه آزاد کافی ایجاد کند.

ART GC

ART (Android Runtime) یک زباله‌روب نسلی با تراکم همزمان معرفی کرد. پیشکنبه به سه منطقه تقسیم می‌شود: Young، Mature (مشابه Old Generation) و Large Object Space (برای شیئ‌های بزرگتر از 12 KB). جمع‌آوری Young Region در اکثر موارد بدون توقف پرونده‌ها به صورت موازی انجام می‌شود. در Android 10+ ، Concurrent Copying ظاهر شد — تراکم در پس‌زمینه بدون Stop-The-World انجام می‌شود.

به لطف معماری ART، مکاث معمولی GC به 2–4 ms کاهش یافت و در سناریوهایی که شیئ‌های جوان غالب هستند — به 0.5–1 ms. این به دستگاه‌های Android امکان داد حتی در کار فعال با حافظه 60 FPS پایدار تامین کنند.

انواع زباله‌روب‌ها در Java

در اکوسیستم Java چندین پیاده‌سازی GC وجود دارد که هر کدام منحنصر به فرد نیروی کارایی خود را دارند. برای توسعه Android انتخاب به ART محدود است، اما دانش Java GC در نوشتن بخش سرور اپلیکیشن‌های موبایل و توسعه با Kotlin Multiplatform مفید است.

Serial GC

Serial GC — زباله‌روب تک پرونده‌ای با توقف کامل اپلیکیشن (Stop-The-World). هر عملیات Mark، Sweep و Compact توسط یک پرونده انجام می‌شود. کارایی پایین است — برای سرورهای موبایل مناسب نیست. فقط برای اپلیکیشن‌های کوچک با پیشکنبه تا 100 MB مناسب است.

Parallel GC

Parallel GC (همچنین به عنوان Throughput Collector شناخته می‌شود) از چند پرونده برای همه فازهای جمع‌آوری استفاده می‌کند. بر حداکثر انتقال دهی (throughput) متمرکز است — زمان صرف شده در GC نسبت به زمان کار اپلیکیشن را مینیمیزم می‌کند. از طریق علمت -XX:+UseParallelGC در JVM فعال می‌شود.

G1 GC

G1 (Garbage-First) GC — زباله‌روب پیش‌فرض در Java 9+. پیشکنبه به مناطق 1–32 MB تقسیم می‌شود. G1 زمان مکث را پیش‌بینی می‌کند و تلاش می‌کند در لیمیت تعیین شده (پیش‌فرض 200 ms) قرار گیرد. اولویت: ابتدا مناطقی پاک می‌شوند که بیشترین حجم زباله را دارند (از اینجا نام G1). G1 برای سرورهای با پیشکنبه بزرگ (4–64 GB) و مکاث قابل پیش‌بینی مناسب است.

java
// فعال‌سازی G1 GC با مکث هدف 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("حد استفاده از پیشکنبه تجاوز شد: " + used);
            System.out.println("کاهش تخصیص‌ها را در نظر بگیرید");
        }
    }
}

نظارت بر پیشکنبه از طریق Runtime به تشخیص زودهنگام نشت حافظه کمک می‌کند. اگر used در کار پایدار از 80% حداکثر پیشکنبه تجاوز کند — این نشانه‌ای از نشت حافظه یا مصرف بیش از حد حافظه توسط اپلیکیشن است.

مشکلات GC و بهینه‌سازی حافظه در اپلیکیشن‌های موبایل

حتی ART GC مودرن همه مشکلات را حل نمی‌کند — استفاده نادرست از حافظه علت اصلی jank و ANR (Application Not Responding) باقی می‌ماند. بیایید سناریوهای اصلی و روش‌های بهینه‌سازی را بررسی کنیم.

GC Pauses و Jank

GC Pauses — توقف پرونده‌های اپلیکیشن در طول جمع‌آوری. در صفحه نمایش، این به صورت فرم‌های افتاده ظاهر می‌شود زمانی که زمان بین دو فرم از 16.6 ms (60 FPS) تجاوز کند. اگر GC 30 ms طول بکشد، به جای دو فرم، فقط یک فرم رسم می‌شود — کاربر کندی را در رابط می‌بیند.

دلایل اصلی مکاث طولانی: تعداد زیاد شیئ‌های زنده در Old Generation، تکه تکه شدن پیشکنبه، Full GC مکرر. برای تشخیص از Android Studio Profiler و systrace استفاده می‌شود.

کاهش بار GC

قاعده اصلی کد مناسب برای GC — کاهش تعداد شیئ‌های تخصیص شده. هر شیئ جدید نه تنها نیازمند تخصیص حافظه است، بلکه جمع‌آوری بعدی را نیز نیاز دارد. حتی اگر GC سریع باشد، 1000 تخصیص اضافی در ثانیه 1000 بررسی برای زباله‌روب ایجاد می‌کند.

  • از ایجاد شیئ در حلقه‌ها خودداری کنید — ایجاد را خارج حلقه قرار دهید، از متغیرهای محلی مجدد استفاده کنید
  • از استخرهای شیئ استفاده کنید — برای Bitmap، byte[] و ساختارهای سنگین دیگر از Object Pool یا RecyclerView.ViewHolder استفاده کنید
  • اولویت با نوع‌های ابتدایی — int به جای Integer، float به جای Float از autoboxing جلوگیری می‌کنند
  • از SparseArray استفاده کنید — به جای HashMap<Integer, V> در Android SDK از SparseArray، LongSparseArray استفاده کنید که با نوع‌های ابتدایی کار می‌کنند
  • StringBuilder به جای هم‌آیش رشته‌ها — هر جمع رشته یک شیئ String جدید ایجاد می‌کند

نشت حافظه

نشت حافظه زمانی رخ می‌دهد که یک شیئ قابل دسترس باقی بماند، در حالی که دیگر نیاز نیست. GC نمی‌تواند چنین شیئی را حذف کند و حافظه تدریجاً تمام می‌شود. علل معمولی: اشتراک‌های پاک نشده، ارجاعات استاتیک به Activity، کلاس‌های ناشناس که کنتکست خارجی را گرفته‌اند و Cursor/InputStream بسته نشده.

java
// نشت حافظه: کلاس ناشناس ارجاعی به Activity نگه می‌دارد
public void startTask() {
    new Thread(new Runnable() {                    // ضمناً this (Activity) را نگه می‌دارد
        @Override
        public void run() {
            // عملیات طولانی...
            System.out.println("انجام شد");
        }
    }).start();
}

// رفع: کلاس تودره استاتیک + 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) {
            // کار ایمن با Activity
        }
    }
}

در این مثال، Runnable ناشناس یک ارجاع ضمنی به Activity گرفته است. تا زمانی که پرونده زنده است — Activity نمی‌تواند توسط GC جمع شود، حتی اگر کاربر صفحه را بسته باشد. رفع WeakReference + static class این زنجیره را می‌شکند و به Activity اجازه بازیابی می‌دهد.

سوالات متداول

تفاوت GC در Android با GC در Java چیست؟

GC در Android (ART) — یک زباله‌روب نسلی با تراکم همزمان است که برای دستگاه‌های موبایل با حافظه محدود بهینه شده است. Java GC (G1, ZGC) زباله‌روب‌های سروری با پیشکنبه‌های بزرگ و مکاث قابل پیش‌بینی هستند. ART GC از فلاگ‌های JVM استفاده نمی‌کند — تمام تنظیمات به صورت خودکار در سطح سیستم‌عامل انجام می‌شود.

Stop-The-World در GC چیست؟

Stop-The-World — لحظه‌ای است که زباله‌روب تمام پرونده‌های اپلیکیشن را متوقف می‌کند تا به صورت ایمن از گراف شیئ‌ها عبور کند یا حافظه را آزاد کند. هر چقدر STW طولانی‌تر باشد، jank بیشتر قابل توجه است. ART زمان معمولی STW را به 2–4 ms به لطف معماری نسلی کاهش داده است.

چگونه نشت حافظه را در Android تشخیص دهیم؟

از Android Studio Memory Profiler استفاده کنید — رشد پیشکنبه، تعداد تخصیص‌ها را نشان می‌دهد و امکان Heap Dump را فراهم می‌کند. برای تحلیل عمیق، از LeakCanary استفاده کنید — این کتابخانه به صورت خودکار نشت‌ها را تشخیص می‌دهد و زنجیره ارجاعاتی را که مانع جمع‌آوری GC می‌شود نشان می‌دهد.

فال GC کی اتفاق می‌افتد و چرا خطرناک است؟

Full GC — جمع‌آوری کامل همه نسل‌های پیشکنبه، شامل Old Generation. در اپلیکیشن‌های موبایل، Full GC می‌تواند 50–200 ms طول بکشد و باعث jank قابل توجه یا ANR شود. علل اصلی: تکه تکه شدن پیشکنبه، نشت حافظه، تجاوز حد Old Generation.

Kotlin چگونه به جلوگیری از نشت حافظه کمک می‌کند؟

Kotlin کوروتین‌هایی با همزمانی ساختاریافته فراهم می‌کند — لغو scope به صورت خودکار تمام کوروتین‌های فرزند را لغو می‌کند و از نشت حافظه جلوگیری می‌کند. همچنین در Kotlin دلگات lazy برای مقداردهی تنبل و عاملگرهای حوزه دید وجود دارند که تعداد شیئ‌های موقت را کاهش می‌دهند.

نتایج

  • Garbage Collection — مدیریت خودکار حافظه از طریق حذف شیئ‌های غیرقابل دسترس، پایه Android Runtime
  • Mark-and-Sweep — الگوریتم پایه با جمع‌آوری دو مرحله‌ای، مبتلا به تکه تکه شدن پیشکنبه
  • Copying Collection — با کپی شیئ‌های زنده به نیم‌فضای متراکم، تکه تکه شدن را برطرف می‌کند
  • Generational GC — پیشکنبه را به نسل‌ها (Young/Old) تقسیم می‌کند، جمع‌آوری شیئ‌های جوان کوتاه‌عمر را سریع می‌کند
  • ART در Android — زباله‌روب نسلی با تراکم همزمان و مکاث 2–4 ms، جایگزین Dalvik در Android 5.0
  • بهینه‌سازی GC — کاهش تخصیص‌ها، استخرهای شیئ، نوع‌های ابتدایی به جای جعبه‌ها و SparseArray به جای HashMap بار زباله‌روب را کاهش می‌دهند
  • تشخیص — Android Studio Profiler، systrace و LeakCanary ابزارهای اصلی برای کشف مشکلات حافظه هستند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید