در این مقاله ما سعی کردیم درمورد (GC)Garbage Collector در جاوا صبحت کنیم انواع پیاده سازی های اون و اینکه کجا از کدوم پیاده سازی استفاده کنیم بهتره.
معرفی GC
در زبان جاوا مدیریت حافظه برعهده خود JVM بوده و کار را برای توسعه دهندگان آسان تر کرده است. GC یک پروسس می باشد که فرآیند آزاد سازی حافظه را بصورت خودکار انجام می دهد. که Heap memory اپلیکشن را بررسی کرده و اشیایی که مورد استفاده قرار نگرفته را شناسایی و از حافظه پاک می کند.
در بقیه زبان ها مانند C فرآیند تخصیص و آزاد سازی حافظه به عهده خود توسعه دهنده بود ولی در جاوا اینکار توسط GC انجام می شود که بصورت زیر می توانیم توضیح دهیم:
۱.نشانه گذاری
در این مرحله gc شناسایی می کند کدام قسمت از حافظه استفاده شده است و کدام قسمت استفاده نشده است.
۲. حذف عادی
بعد از شناسایی اشیا استفاده نشده gc آن ها را از حافظه حذف می کند و بقیه اشیا را به حال خود واگذار می کند.
تخصیص دهنده حافظه محل هایی از حافظه را که آزاد شده را برای تخصیص اشیا جدید نگه می دارد.
۲.۱ حذف با فشرده سازی
برای بهبود عملکرد علاوه حذف اشیا استفاده نشده مابقی اشیا استفاده شده را فشرده کرده و در کنار هم در قسمتی از حافظه فشرده می کنند با این کار تخصیص حافظه جدید ساده تر و سریع تر می شود.
مزایا:
معایب:
JVM Generations
با توجه به رفتار اشیا در حافظه می توان رفتار GC را کارآمد تر کرد. به همین خاطر حافظه Heap به چند قسمت کوچک تر تقسیم شده است.
1. Young Generation (نسل جوان)
2. Old or Tenured Generation (نسل قدیمی)
3. Permanent Generation (نسل دائمی)
نسل جوان (Young Generation)
هر شئ ای که ساخته می شود ابتدا در این قسمت Heap قرار می گیرد زمانی که این قسمت از Heap پر شود منجر به فرخوانی GC می شود که به آن minor garbage collection می گویند و زمانی اجرا می شود حافظه Eden پر شود.
بنابراین اشیایی که در برنامه هنوز استفاده می شوند به حافظه های S0 یا S1 منتقل می شوند و بقیه اشیای مرده از حافظه پاک می شوند.
زمانی که minor gc فرخوانی می شود منجر به Stop World Events می شود که به این معناست تمامی Threadهای برنامه متوقف می شوند بتا فرآیند gc کامل شود.
نسل قدیمی (Old Generation)
اشیایی که عمر طولانی تری دارند در این قسمت از حافظه قرار می گیرند و به این صورت که یک آستانه در JVM تعریف می شود و زمانی که سن شی به این آستانه می رسد به این قسمت از حافظه منتقل می شود.
اشیای موجود در Old Generation هم نیاز به پاکسازی دارند که به آن major garbage collection می گویند.
فرآیند major gc کندتر از minor می باشد چون باید تمام حافظه بررسی کند و این برای برنامه هایی که نیاز به پاسخ دهی بالا دارند مناسب نیست همینطور major gc هم منجر به Stop World Event می شود که بسته به نوع gc که انتخاب می کنید می تواند زمان متفاوتی طول بکشد.
نسل دائمی (Permanent Generation)
این قسمت از حافظه شامل یکسری اطلاعات مربوط به کلاس ها متد ها می باشد که مورد نیاز JVM می باشد.
این قسمت حافظه از جاوای ورژن 8 حذف شود و JVM از حافظه اصلی برای ذخیره MetaData، کلاس ها و متد ها استفاده می کند که به آن MetaSpace می گویند، همینطور یک تنظیمات جدید MaxMetaspaceSize برای تنظیم حداکثر مقدار مقدار حافظه مورد استفاده توسط MetaSpace اضافه شده است که زمانی حافظه استفاده به این مقدار برسد Metaspace garbage collection فعال می شود و کلاس ها و MetaData ها اضافی را حذف می کند اگر MaxMetaspaceSize را تنظیم نکنید مقدار حافظه استفاده شده به صورت خودکار باتوجه به نیاز برنامه کم یا زیاد می شود.
فرآیند کامل یک gc
دستورات مختلفی برای تنظیمات gc وجود دارد که ما در جدول زیر مهم ترین آن ها را آورده ایم:
به صورت پیش فرض در جاوای ورژن 5 و 6 از این نوع gc استفاده می شود که بصورت سریال minor و major را اجرا می کند همینطور از روش mark-compact نیز استفاده می کند که اشیای قدیمی تر به ابتدا حافظه منتقل می شوند به همین خاطر فرآیند تخصیص حافظه به اشیای جدید سریع تر انجام می شود.
در Serial GC انتخابی برای اکثر برنامههایی است که نیاز به زمان مکث کم ندارند. تنها از یک پردازنده مجازی برای کار جمع آوری زباله استفاده می کند. با این حال، در سختافزار امروزی، Serial GC میتواند بسیاری از برنامههای کاربردی غیر ضروری را با چند صد مگابایت پشته جاوا، با مکثهای نسبتاً کوتاه در بدترین حالت (حدود چند ثانیه برای جمعآوری کامل زبالهها) به طور مؤثر مدیریت کند.
یکی دیگر از کاربردهای محبوب Serial GC در محیطهایی است که تعداد زیادی JVM روی یک دستگاه اجرا میشوند (در برخی موارد، JVMهای بیشتری نسبت به پردازندههای موجود!). در چنین محیطهایی وقتی یک JVM جمعآوری زباله انجام میدهد، بهتر است فقط از یک پردازنده استفاده شود تا تداخل در JVMهای باقیمانده به حداقل برسد، حتی اگر جمعآوری زباله ممکن است بیشتر طول بکشد. و Serial GC به خوبی با این مبادله مطابقت دارد.
در نهایت، با گسترش سختافزارهای کوچک با حداقل حافظه و هستههای کم، Serial GC میتواند بازگشتی داشته باشد.
برای فعال سازی Serial Gc از دستور زیر می تونین استفاده کنید:
-XX:+UseSerialGC
نمونه کامل:
java -XX:+UseSerialGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
از جاوای ۵ تا ۸ به عنوان gc پیش فرض در جاوا استفاده شده است. برخلاف Serial Collector این نوع از چند thread برای مدیریت حافظه استفاده می کند اما کماکان تمامی thread های برنامه متوقف می کنند.
اگر سیستم شما دارای N تعداد هسته پردازشی باشد بصورت پیش فرض به همان تعداد thread هم در gc برای فرآیند مدیریت حافظه استفاده خواهد شد. شما می توانید با دستور زیر تعداد thread مورد استفاده در برنامه خود را مشخص کنید:
-XX:ParallelGCThreads=<desired number>
این کالکتور throughput collector هم نامگذاری می شود زیرا با استفاده از چندین پردازشگر می تواند توان عملیاتی برنامه شما را افزایش دهد. این کالتکور باید زمانی استفاده شود که کار زیادی باید انجام شود و مکث های طولانی در برنامه قابل قبول می باشد برای مثلا زمانی که برنامه شما پردازش های دسته ای انجام می دهد مانند پرینت تعداد زیادی گزارش یا کوئری های زیاد و متعدد از دیتابیس ...
این کالکتور در دو نسخه ارائه شده است:
--XX:+UseParallelGC
در این نوع قسمت Young Generation حافظه توسط چندین thread مدیریت خواهد شد اما قسمت Old Generation توسط یک thread مدیریت خواهد شد، همچین برای حافظه ی Old Generation عملیات فشرده سازی توسط تک thread انجام خواهد شد، برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UseParallelGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
--XX:+UseParallelOldGC
در این نوع کالکتور هردو قسمت حافظه Young و Old توسط چندین thread مدیریت می شود همچنین عملیات فشرده سازی نیز توسط چندین thread انجام می پذیرد که فقط در قسمت Old Generation حافظه عملیات فشرده سازی اتفاق می افتد.
در قسمت Young Generation حافظه چون عملیات پاکسازی توسط کپی کردن اشیا اتفاق می افتد نیازی به فشرده سازی نیست.
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UseParallelOldGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
این کالکتور با استفاده چندین thread سعی می کند کمترین مکث را در برنامه ایجاد کند برای همین عملیات هایی مانند فشرده سازی یا انتقال اشیا را انجام نمی دهد صرفا فقط با مکث های کوتاه علمیات آزاد سازی حافظه را انجام می دهد، برای همین باید مقدار heap بیشتری به برنامه اختصاص دهید.
این نوع کالکتور برای برنامه هایی که به مکث های بسیار کوتاه نیاز دارند مناسب می باشد مانند desktop application ها یا یک web server که باید به درخواست های کاربران پاسخگو باشد.
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads=<n>
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UseConcMarkSweepGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
این کالکتور از جاوای ورژن 7 به بعد دردسترس می باشد این کالکتور به عنوان جایگزین طولانی مدت CMS طراحی شده است. G1 یک جمع کننده موازی می باشد که عملیات مدیریت حافظه را توسط چندین thread انجام می دهد با این حال عملیات فشرده سازی را نیز به تدریج انجام می دهد و برای کمترین مکث در برنامه طراحی شده است. ( آنچه خوبان همه دارند تو یکجا داری/ بی سبب نیست که در کنج دلم جا داری)
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UseG1GC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
این کالکتور از جاوی ورژن 11 بصورت آزمایشی برای سیستم عامل لینوکس منتشر شد سپس از جاوای 14 به بعد برای سیستم های عامل ویندوز و مک دردسترس قرار گرفت و از جاوای ورژن 15 ورژن نهایی آن منتشر شد.
این کالکتور از چندین thread برای مدیریت حافظه استفاده می کند و فعالیت برنامه را بیشتر از 10 میلی ثانیه متوقف نمی کند، این کالکتور توان مدیریت حافظه های Heap از 8 مگابایت تا 16 ترابایت را دارد و زمان اجرا شدن gc با زیاد شدن حافظه Heap افزایش نمی یابد.
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UseZGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
این کالکتور هیچ کاری انجام نمی دهد در واقع فرآیند تخصیص حافظه را انجام می دهد اما فرآیند آزاد سازی حافظه را انجام نمی دهد از این کالتکور می توان در برنامه هایی حساس به تاخیر استفاده کرد یا برنامه هایی که توسعه دهنده ردپای حافظه ای که استفاده کرده است را می داند و یا در برنامه هایی که تقریبا (کاملا) هیچ زباله ای ندارند.
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
این کالکتور با جاوای 12 منشتر شده است و یک مزیتی که نسبت به G1 دارد این می باشد که می تواند چرخه مدیریت حافظه را همزمان با thread های برنامه انجام دهد و حافظه آزاده شده را بلافاصله به سیستم عامل برگرداند.
از آن جایی که تمامی فرآیند همزمان با اجرای برنامه اتفاق می افتد CPU بیشتری مصرف می شود.
برای استفاده از این کالکتور می توانید از دستور زیر استفاده کنید :
java-XX:+UseShenandoahGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
مقایسه GC Pause Times:
برای فرخوانی دستی gc می توانید از کد های زیر استفاده کنید:
System.gc(); Runtime.getRuntime().gc();
البته اصلا توصیه به فرخوانی دستی gc نشده است و تاجای امکان از فرخوانی آن پرهیز کنید، چون به شما گارانتی نمی دهد با فرخوانی شما gc اجرا شود.
تنظمیات gc بهتر است به صورت پیش فرض باقی بماند، زمانی که شما برنامه ای دارید با تعداد بالایی کاربر آن زمان می توانید به شخصی سازی gc بپردازید چون این کار به تجربه و دانش خیلی بالا دارد.
منابع:
https://www.baeldung.com/jvm-garbage-collectors
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java