دلایل ایجاد OutOfMemoryError در زبان برنامه‌نویسی Java چیست؟

دلایل ایجاد OutOfMemoryError در زبان برنامه‌نویسی Java چیست؟

اگر در حین کدنویسی جاوا با ارور OutOfMemoryError مواجه شوید، به‌طور حتم دلیل آن چیزی نیست جز این‌که حجم مموری نیتیو پاسخگوی نیاز JVM نیست، یا فضای Heap پاسخگوی نیاز اپلیکیشن نیست، یا فضای PermGen یا Metaspace کافی نبوده و یا این‌که JVM زمان زیادی را صرف Garbage Collection کرده است! برای فهمیدن این‌که دلیل ارور OutOfMemoryError دقیقاً کدام‌یک از ۴ مورد فوق است، فقط کافی است به پیام ارور ظاهر شده دقت کنید. در این مقاله قصد داریم به ۴ دلیل احتمالی این ارور، نگاه دقیق‌تری داشته باشیم.

۱. حجم مموری نیتیو پاسخگوی نیاز JVM نیست
مشاهدهٔ این ارور اساساً به این معنی است که بخشی از مموری که به JVM (ماشین مجازی جاوا) اختصاص داده شده، به‌طور کامل درگیر شده و هیچ قسمتی از آن آزاد باقی نمانده است. در JVMهای نسخهٔ ۳۲بیتی، ۳/۵ الی ۴ گیگابایت مموری برای انجام عملیات موردنیاز درنظر گرفته شده است که اگر نیاز JVM از این مقدار فراتر رود، با ارور OutOfMemoryError مواجه خواهیم شد.

حتی در نسخهٔ ۶۴بیتی نیز ممکن است سیستم‌عامل مموری کافی برای اختصاص دادن به JVM را در اختیار نداشته باشد و ارور OutOfMemoryError رخ دهد. به‌عنوان مثال، در یک نوت‌بوک ۶۴بیتی (با Mac OS X 10.11.6 و Java 1.8.1_112)، پس از ۲۰۲۳ بار اجرای پی‌در‌پی یک حلقه از جنس for، ماشین مجازی جاوا دیگر قادر به اجرای مجدد آن نبوده و اروری به‌صورت زیر ایجاد خواهد شد (خطای ناشی از کمبود حجم مموری نیتیو، همواره چیزی مشابه متن خطای زیر است):

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

۲. فضای Heap پاسخگوی نیاز اپلیکیشن نیست
Heap فضایی از مموری است که تمام آبجکت‌های ایجاد شده در حین اجرای یک برنامهٔ‌ جاوا در آن ذخیره می‌شوند (لازم به‌ذکر است که آبجکت‌هایی که تولید شده و در جایگاه خود مورد استفاده قرار گرفته و سپس بدون کاربرد باقی مانده‌اند در طی فرآیندی به‌نام Garbage Collection از فضای Heap حذف می‌شوند تا فضا برای نگهداری آبجکت‌های جدید موردنیاز برنامه مورد استفاده قرار گیرد).

اگر خطای OutOfMemoryError به دلیل کافی نبودن فضای Heap رخ دهد، از آن‌جا که عبارت Java Heap Space در متن پیام خطا درج می‌شود، تشخیص دلیل آن بسیار ساده خواهد بود. در چنین مواردی، افزایش سایز Heap می‌تواند راه‌حل خوبی باشد اما اگر سیستم با کمبود مموری مواجه باشد این راه‌حل موقتی بوده و تنها می‌تواند رخ دادن ارور OutOfMemoryError را به تأخیر بیندازد و درنهایت خطایی به‌صورت زیر ظاهر خواهد شد:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

۳. فضای PermGen یا Metaspace کافی نیست
Permanent Generation که به‌طور خلاصه PermGen نامیده می‌شود بخشی از مموری است که اطلاعات کلاس‌های بارگذاری شده درحین اجرای یک برنامهٔ جاوا و همچنین فیچرهای پیشرفته‌ای همچون String Pool را در خود نگاه می‌دارد.

PermGen (در Java 7 و نسخه‌های قدیمی‌تر) حجم محدودی دارد و این بدان معنا است که اگر کلاس‌های متعدد و بسیار زیادی را در برنامهٔ خود ایجاد نموده باشید، فضای PermGen به‌زودی پر خواهد شد و خطای OutOfMemoryError رخ خواهد داد.

در این‌گونه موارد، افزایش حجم PermGen می‌تواند مشکل را حل کند؛ در Java 8 به‌جای PermGen فضایی به نام Metaspace درنظر گرفته شده است که حجم آن به‌طور پیش‌فرض -و تا زمانی که حدی برای آن تعیین نشود- نامحدود است بنابراین منجر به ارور OutOfMemoryError نخواهد شد. برای تشخیص این‌که این ارور به دلیل کافی نبودن فضای PermGen یا Metaspace رخ داده است یا نه، باید به متن پیام ارور دقت کنید. این پیام مشابه متن زیر خواهد بود:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

۴. JVM زمان زیادی را صرف Garbage collection می‌نماید
Garbage Collection -یا به‌طور خلاصه GC - فرآیندی است که در طی آن آبجکت‌هایی که دیگر کاربردی در اپلیکیشن ندارند از چرخهٔ اجرا خارج و حذف می‌شوند و بدین ترتیب مموری موجود برای اجرای سایر قسمت‌های اپلیکیشن آزاد می‌شود.

یکی از دلایل ایجاد OutOfMemoryError این است که JVM زمان زیادی را صرف GC می‌نماید؛ این مورد از بقیهٔ موارد پیچیده‌تر بوده و از این‌رو برطرف کردن آن نیز دشوار‌تر است. این وضعیت زمانی ایحاد می‌شود که هر ۴ شرط زیر برقرار باشد:

- JVM بیش از ۹۸٪ از زمان پردازش خود را صرف GC نماید (۹۸٪ مقدار پیش‌فرض است و با تنظیم GCTimeLimit=N می‌توان محدودیت این مقدار پیش‌فرض را لغو نموده و آن‌را نامحدود کرد).

- در طی GC، کمتر از ۲٪ از فضای Heap آزاد باقی بماند (۲٪ مقدار پیش‌فرض است و با تنظیم GCHeapLimit=N می‌توان این محدودیت را از میان برداشت).

- ۲ شرط فوق در مورد چندین سیکل Full GC پی‌درپی صادق باشد.

- گزینهٔ UseGCOverheadLimit غیرفعال نشده باشد (این گزینه به‌طور پیش‌فرض فعال است).

به‌هر‌حال، ادامهٔ یافتن سیکل Full GC برای مدت طولانی، به‌معنای کافی نبودن مموری است. اگر ماشین مجازی جاوا ۹۸٪ از زمان خود را صرف آزاد کردن تنها ۲٪ از مموری Heap می‌کند، یعنی این‌که CPU تقریباً به‌طور کامل درگیر Garbage Collection بوده و عملاً ادامهٔ‌ اجرای اپلیکیشن بی‌معنا است. بنابراین جای تعجب نیست که در چنین شرایطی، JVM اجرای کدها را متوقف نموده و ارور OutOfMemoryError ظاهر شود. در این‌گونه موارد متن پیام ارور به‌صورت زیر خواهد بود:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

آیا شما تاکنون با چنین مشکلی در حین کدنویسی اپلیکیشن‌های جاوا مواجه شده‌اید؟ در چنین شرایطی چگونه مشکل OutOfMemoryError را حل نموده‌اید؟ نظرات، پیشنهادات و تجربیات خود را با ما و سایر کاربران سکان آکادمی به اشتراک بگذارید (همچنین برای شروع یادگیری این زبان، می‌توانید به دورهٔ آنلاین و رایگان آموزش جاوا در سکان آکادمی مراجعه نمایید).

منبع


رائفه خلیلی