استخدام الذاكرة الظاهرية من Java تحت Linux، الكثير من الذاكرة المستخدمة

StackOverflow https://stackoverflow.com/questions/561245

سؤال

لدي مشكلة في تطبيق Java يعمل تحت Linux.

عند تشغيل التطبيق، باستخدام الحد الأقصى للحجم الكهائن الأقصى الافتراضي (64 ميغابايت)، أرى استخدام تطبيق قمم يتم تخصيص 240 ميغابايت من الذاكرة الظاهرية للتطبيق. يؤدي هذا إلى إنشاء بعض المشكلات مع بعض البرامج الأخرى على الكمبيوتر، وهو محدود من الموارد نسبيا.

لن يتم استخدام الذاكرة الافتراضية المحجوزة على أي حال، بقدر ما أفهمها، لأنه بمجرد أن نصل إلى حد كومة OutOfMemoryError هذا خطئ. ركضت نفس التطبيق تحت النوافذ وأرى أن حجم الذاكرة الظاهري وحجم كومة الكومة مشابه.

هل هناك على أي حال يمكنني تكوين الذاكرة الظاهرية قيد الاستخدام لعملية Java ضمن Linux؟

تحرير 1.: المشكلة ليست هي الكومة. المشكلة هي أنه إذا قمت بتعيين كومة من 128 ميغابايت، على سبيل المثال، لا يزال Linux يخصص 210 ميغابايت من الذاكرة الظاهرية، والتي ليست هناك حاجة إليها، على الإطلاق. **

تحرير 2.: استخدام ulimit -v يسمح بالحد من كمية الذاكرة الافتراضية. إذا كانت مجموعة الحجم أقل من 204 ميغابايت، فلن يتم تشغيل التطبيق على الرغم من أنه لا يحتاج إلى 204 ميغابايت، فقط 64 ميغابايت. لذلك أريد أن أفهم لماذا يتطلب جافا الكثير من الذاكرة الظاهرية. هل يمكن تغيير هذا؟

تحرير 3.: هناك العديد من التطبيقات الأخرى التي تعمل في النظام، وهو مضمن. والنظام لديه حد ذاكرة افتراضية (من التعليقات، التفاصيل المهمة).

هل كانت مفيدة؟

المحلول

لقد كانت هذه شكوى طويلة الأمد مع جافا، لكنها لا معنى لها إلى حد كبير، وعادة ما تستند إلى النظر إلى المعلومات الخاطئة. الصياغة المعتادة هي شيء مثل "Hello World على Java يستغرق 10 ميغابايت! لماذا يحتاج ذلك؟" حسنا، إليك طريقة لجعل Walleo World على مطالبة JVM 64 بت لاتخاذ أكثر من 4 غيغابايت ... على الأقل من خلال شكل واحد من أشكال القياس.

جافا -xms1024m -xmx4096m com.example.hello

طرق مختلفة لقياس الذاكرة

على لينكس، أعلى تمنحك الأمر عدة أرقام مختلفة للذاكرة. إليك ما يقوله حول مثال مرحبا العالم:

 PID User PR NI Virt Res Shr S٪ CPU٪ MEM TIME + COMMAND 2120 كيمبي 20 0 4373M 15M 7152 S 0 0.2 0: 00.10 Java
  • Virt هي مساحة الذاكرة الظاهرية: مجموع كل شيء في خريطة الذاكرة الظاهرية (انظر أدناه). إنه لا معنى له إلى حد كبير، إلا عندما لا يكون (انظر أدناه).
  • RES هو حجم المجموعة المقيم: عدد الصفحات المقيمة حاليا في ذاكرة الوصول العشوائي. في جميع الحالات تقريبا، هذا هو الرقم الوحيد الذي يجب عليك استخدامه عند قول "كبير جدا". لكن الأمر لا يزال عددا جيدا للغاية، خاصة عند الحديث عن جافا.
  • SHR هو مقدار الذاكرة المقيمة التي تتم مشاركتها مع العمليات الأخرى. لعملية جافا، يقتصر ذلك عادة على المكتبات المشتركة و Jarfiles المعينة بالذاكرة. في هذا المثال، كان لدي عملية جافا واحدة فقط قيد التشغيل، لذلك أظن أن 7K هو نتيجة للمكتبات المستخدمة من قبل نظام التشغيل.
  • لم يتم تشغيل المبادلة بشكل افتراضي، ولا يظهر هنا. يشير إلى مقدار الذاكرة الافتراضية المقيمة حاليا على القرص، أم لا في الواقع أم لا في مساحة المبادلة. وبعد نظام التشغيل جيد جدا في الحفاظ على الصفحات النشطة في ذاكرة الوصول العشوائي، والعلاج الوحيد للتبريد (1) شراء المزيد من الذاكرة، أو (2) تقليل عدد العمليات، لذلك من الأفضل تجاهل هذا الرقم.

إن وضع إدارة مهام Windows أكثر تعقيدا قليلا. تحت نظام التشغيل Windows XP، هناك "استخدام الذاكرة" و "حجم الذاكرة الظاهري"، ولكن الوثائق الرسمية صامت على ما تعنيه. نظام التشغيل Windows Vista و Windows 7 أضف المزيد من الأعمدة، وهم في الواقع موثقة. وبعد من هذه، قياس "مجموعة العمل" هو الأكثر فائدة؛ يتوافق تقريبا مع مجموع RES و SHR على Linux.

فهم خريطة الذاكرة الظاهرية

الذاكرة الافتراضية التي تستهلكها عملية هي إجمالي كل ما في خريطة ذاكرة العملية. يتضمن ذلك البيانات (على سبيل المثال، Java Heap)، ولكن أيضا جميع المكتبات المشتركة والملفات المعينة بالذاكرة المستخدمة من قبل البرنامج. على لينكس، يمكنك استخدام PMAP. أمر لرؤية كل الأشياء المعينة في مساحة العملية (من هنا على الخروج، سأشير فقط إلى لينكس، لأنها ما أستخدمه؛ أنا متأكد من وجود أدوات مكافئة لنظام التشغيل Windows). إليك مقتطفات من خريطة الذاكرة لبرنامج "Hello World"؛ تتوفر خريطة الذاكرة بأكملها أكثر من 100 سطر، وليس من غير المعتاد أن يكون لديك قائمة ألف سطر.

0000000040000000 36 كيلو Rx- /usr/local/java/jdk-1.6-x64/bin/java 0000000040108000 8 كيلو RWX- /USR/Local/java/jdk-1.6-x64/bin/java 0000000040EBA000 676K RWX- [anon] 00000006fae00000 21248k rwx-- [anon] 00000006fc2c0000 62720k rwx-- [anon] 0000000700000000 699072K rwx-- [anon] 000000072AAB0000 2097152K RWX-- [anon] 00000007AAAAB0000 349504K RWX- [anon] 00000007C0000000 1048576K RWX - [anon] .. . 00007fa1ed00d000 1652k r-xs- /usr/local/java/jdk-1.jar/lib/rt.jar ... 00007fa1ed1d1d3000 1024k rwx-- [anon] 00007fa1ed2d3000 4K 4K ----- [anon] 00007FA1ED2D4000 1024K RWX-- [anon] 00007FA1ED3D4000 4K ----- [anon] ... 00007fa1f20d3000 164k rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f20fc000 1020k - --- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f21fb000 28k rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava .so ... 00007FA1F34AA000 1576K RX- /Lib/x86_64-linux-gnu/libc-2.13.so 00007FA1F3634000 2044K ----- / x86_64-linux-gnu / libc-2. 13.So 00007fa1f3833000 16 كيلو Rx- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3837000 4 كيلو RWX-- /LIB/X86_64-linux-gnu/libc-2.13.so ...

شرح سريع للتنسيق: يبدأ كل صف بعنوان الذاكرة الظاهرية للجزء. يتبع ذلك حجم القطاع والأذونات ومصدر القطاع. هذا العنصر الأخير هو إما ملف أو "anon"، والذي يشير إلى كتلة من الذاكرة المخصصة عبر mmap..

بدءا من القمة، لدينا

  • Loader JVM (أي، البرنامج الذي يتم تشغيله عند الكتابة java). هذا صغير جدا كل ذلك يتم تحميله في المكتبات المشتركة حيث يتم تخزين رمز JVM الحقيقي.
  • مجموعة من كتل أنون تحمل كومة جافا والبيانات الداخلية. هذا هو الشمس JVM، لذلك يتم تقسيم الكومة إلى أجيال متعددة، كل منها كتلة الذاكرة الخاصة بها. لاحظ أن JVM يخصص مساحة الذاكرة الظاهرية بناء على -Xmx القيمة؛ هذا يسمح لها أن يكون لديك كومة متجاورة. ال -Xms يتم استخدام القيمة داخليا ليقول مقدار الكومة "قيد الاستخدام" عند بدء تشغيل البرنامج، ولإيقاف جمع القمامة عند الاقتراب من هذا الحد.
  • Jarfile تعيين الذاكرة، في هذه الحالة الملف الذي يحمل "فئات JDK". عندما تقوم بالذاكرة خريطة جرة، يمكنك الوصول إلى الملفات داخلها بكفاءة للغاية (مقابل قراءة ذلك من البداية في كل مرة). سيقوم Sun JVM بالذاكرة خريطة جميع الجرار على ClassPath؛ إذا كان رمز التطبيق الخاص بك يحتاج إلى الوصول إلى جرة، فيمكنك أيضا تعيين الذاكرة.
  • بيانات الصفحات لكل مؤشر ترابط اثنين. كتلة 1M هي كومة موضوع؛ أنا لا أعرف ما يذهب إلى كتلة 4K. للحصول على تطبيق حقيقي، سترى العشرات إذا لم تكن مئات من هذه الإدخالات المتكررة من خلال خريطة الذاكرة.
  • واحدة من المكتبات المشتركة التي تحمل رمز JVM الفعلي. هناك العديد من هذه.
  • المكتبة المشتركة للمكتبة القياسية C. هذا مجرد واحدة من أشياء كثيرة يتم فيها تحميل JVM جزءا كبيرا من Java.

تكون المكتبات المشتركة مثيرة للاهتمام بشكل خاص: تحتوي كل مكتبة مشتركة على شريطين على الأقل: شريحة للقراءة فقط تحتوي على رمز المكتبة، وقطاع للقراءة يحتوي على بيانات عالمية لكل عملية للمكتبة (لا أعرف ما الجزء دون أذونات هو؛ لقد رأيته فقط على x64 لينكس). يمكن مشاركة الجزء للقراءة فقط من المكتبة بين جميع العمليات التي تستخدم المكتبة؛ علي سبيل المثال، libc لديه 1.5 متر من مساحة الذاكرة الظاهرية التي يمكن مشاركتها.

متى يكون حجم الذاكرة الظاهري مهم؟

تحتوي خريطة الذاكرة الظاهرية على الكثير من الأشياء. بعضها من القراءة فقط، بعضها مشترك، وبعضها مخصص ولكن لم تطرق أبدا (على سبيل المثال، كل من كومة 4 جيجابايت تقريبا في هذا المثال). لكن نظام التشغيل ذكي بما يكفي لتحميل ما تحتاجه فقط، وبالتالي فإن حجم الذاكرة الظاهري غير ذي صلة إلى حد كبير.

حيث يكون حجم الذاكرة الظاهري مهم هو إذا كنت تعمل على نظام تشغيل 32 بت، حيث يمكنك فقط تخصيص 2GB (أو في بعض الحالات، 3GB) من مساحة عنوان العملية. في هذه الحالة، تتعامل مع مورد نادر، وقد تضطر إلى إجراء مفاتيح، مثل تقليل حجم كومة الكومة من أجل خريطة الذاكرة ملف كبير أو إنشاء الكثير من المواضيع.

ولكن بالنظر إلى أن آلات 64 بت هي في كل مكان، لا أعتقد أنها ستكون طويلة قبل أن يكون حجم الذاكرة الظاهري إحصائية غير ذات صلة تماما.

متى يكون حجم المحتوى المقيم مهم؟

حجم مجموعة المقيم هو جزء من مساحة الذاكرة الظاهرية التي هي في الواقع في ذاكرة الوصول العشوائي. إذا نمو RSS الخاص بك أن تكون جزءا كبيرا من إجمالي ذاكرتك الفعلية، فقد يكون الوقت قد حان للبدء للقلق. إذا نمو RSS الخاص بك لتناول كل الذاكرة الفعلية الخاصة بك، ويبدأ نظامك في تبادل، فهو في وقت جيد الماضي لبدء القلق.

لكن آر إس إس مضللة أيضا، خاصة على جهاز محمل طفيف. لا ينته نظام التشغيل الكثير من الجهد لاستصلاح الصفحات المستخدمة من خلال عملية. لا توجد ضئيل من الاستفادة من خلال القيام بذلك، وإمكانية وجود خطأ صفحة مكلفة إذا كانت العملية تلامس الصفحة في المستقبل. نتيجة لذلك، قد يتضمن إحصائيات RSS الكثير من الصفحات التي ليست في الاستخدام النشط.

الحد الأدنى

ما لم تكن مبادلة، لا تشعر بالقلق للغاية بشأن ما تخبرك إحصائيات الذاكرة المختلفة. مع تحذير أن آر إس إس نموا قد تشير إلى نوع من تسرب الذاكرة.

مع برنامج Java، من المهم للغاية الانتباه إلى ما يحدث في كومة الكومة. المبلغ الإجمالي للمساحة المستهلكة أمر مهم، وهناك بعض الخطوات التي يمكنك اتخاذها لتقليل ذلك. والأهم من ذلك هو مقدار الوقت الذي تقضيه في مجموعة القمامة، وأي أجزاء من الكومة التي يتم جمعها.

الوصول إلى القرص (أي قاعدة بيانات) مكلفة، والذاكرة رخيصة. إذا كنت تستطيع تداول واحد للآخر، ففعل ذلك.

نصائح أخرى

هناك مشكلة معروفة مع Java and Glibc> = 2.10 (بما في ذلك Ubuntu> = 10.04، Rhel> = 6).

العلاج هو ضبط هذا env. عامل:

export MALLOC_ARENA_MAX=4

إذا كنت تقوم بتشغيل Tomcat، فيمكنك إضافة هذا إلى TOMCAT_HOME/bin/setenv.sh ملف.

ل docker، أضف هذا إلى dockerfile

ENV MALLOC_ARENA_MAX=4

هناك مقالة IBM حول إعداد malloc_arena_maxhttps://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage؟lang=en.

هذه المدونة وظيفة يقول

من المعروف أن الذاكرة المقيمة تزحف بطريقة مماثلة لتسريب الذاكرة أو تجزئة الذاكرة.

هناك أيضا علة JDK مفتوحة JDK-8193521 "Glibc نفايات الذاكرة مع التكوين الافتراضي"

ابحث عن Malloc_arena_max على Google أو نحو ذلك لمزيد من المراجع.

قد ترغب في ضبط خيارات MOLOC الأخرى أيضا لتحسين تجزئة منخفضة للذاكرة المخصصة:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

مبلغ الذاكرة المخصصة لعملية Java على قدم المساواة مع ما أتوقعه. لقد تلقيت مشاكل مماثلة في تشغيل Java على أنظمة LIMITED / الذاكرة المحدودة. ادارة أي تطبيق مع حدود VM التعسفي أو على الأنظمة التي لا تملك كميات كافية من المبادلة تميل إلى كسر. يبدو أن طبيعة العديد من التطبيقات الحديثة التي لا يتم تصميمها للاستخدام على أنظمة محدودة الموارد.

لديك بعض الخيارات الإضافية التي يمكنك محاولة وتحديد بصمة الذاكرة الخاصة بك في JVM. قد يقلل هذا من فصفة الذاكرة الظاهرية:

-xx: comptionSedCoDecachesize = 32M حجم ذاكرة التخزين المؤقت رمز محفوظة (بالبايت) - الحد الأقصى لحجم ذاكرة التخزين المؤقت رمز. [Solaris 64 بت، AMD64، و -Server X86: 48M؛ في 1.5.0_06 والإصدارات السابقة، سولاريس 64 بت و 64: 1024m.

-xx: maxpermsize = 64M حجم الجيل الدائم. [5.0 والأحدث: 64 بت VMS يتم تحجيمها بنسبة 30٪؛ 1.4 AMD64: 96M؛ 1.3.1 -Client: 32M.

أيضا، يجب عليك أيضا تعيين -xmx (أقصى حجم الكومة) إلى قيمة أقرب ما يمكن استخدام الذاكرة الذروة الفعلية من طلبك. أعتقد أن السلوك الافتراضي ل JVM لا يزال مزدوج حجم الكومة في كل مرة يقوم فيها بتوسيعه إلى الحد الأقصى. إذا بدأت ب 32 مليون كومة من كومة وتطبيقك بلغت ذروتها إلى 65 مترا، فسوف ينتهي الذاكرة المؤقتة بنسبة 32 مترا -> 64m -> 128m.

قد تحاول أيضا ذلك لجعل VM أقل عدوانية حول زراعة كومة:

-xx: minheapfreeratio = 40 الحد الأدنى من كومة الكومة مجانا بعد GC لتجنب التوسع.

أيضا، من ما أذكره من تجربة هذا منذ بضع سنوات، كان عدد المكتبات الأصلية المحملة تأثير كبير على الحد الأدنى من البصمة. قم بتحميل Java.net.Socket أضيفت أكثر من 15M إذا كنت أتذكر بشكل صحيح (وأنا ربما لا).

تتطلب Sun JVM الكثير من الذاكرة للنقطة الساخنة وخرائطها في مكتبات التشغيل في الذاكرة المشتركة.

إذا كانت الذاكرة مشكلة في الاعتبار باستخدام JVM آخر مناسب للضمان. IBM لديه J9، وهناك المصدر المفتوح "Jamvm" الذي يستخدم مكتبات GNU ClassPath. أيضا الشمس لديها صرير JVM يعمل على البقع الشمسية لذلك هناك بدائل.

مجرد فكرة، ولكن يمكنك التحقق من تأثير أ ulimit -v اختيار.

هذا ليس حل فعلي لأنه سيحد من مساحة العنوان المتاحة ل الكل عملية، ولكن من شأنها أن تسمح لك بالتحقق من سلوك طلبك مع ذاكرة افتراضية محدودة.

قد تكون إحدى طرق تقليل ثقب كومة النظام مع موارد محدودة للعب مع متغير -xx: maxheapfreeratio المتغير. وعادة ما يتم تعيين هذا على 70، وهو النسبة المئوية القصوى لكومة الكومة المجانية قبل أن تقلص GC. قم بإعدادها إلى قيمة أقل، وسترى في مثل Profiler Jvisualvm الذي عادة ما يتم استخدام ثقب كومة أصغر عادة لبرنامجك.

تحرير: لتعيين القيم الصغيرة ل -xx: maxheapfreeratio يجب عليك أيضا تعيين -xx: minheapfreeratio على سبيل المثال

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

Edit2: إضافة مثال لتطبيق حقيقي يبدأ ويفعل نفس المهمة، واحدة مع المعلمات الافتراضية والآخر مع 10 و 25 معلمات. لم ألاحظ أي فرق حقيقي السرعة، على الرغم من أن Java من الناحية النظرية يجب أن تستخدم المزيد من الوقت لزيادة الكومة في المثال الأخير.

Default parameters

في النهاية، Max Heap هو 905، يستخدم الكومة المستخدمة 378

MinHeap 10, MaxHeap 25

في النهاية، Max Heap هو 722، Heap المستخدمة 378

هذا في الواقع لديه بعض العلن، حيث يعمل تطبيقنا على خادم سطح المكتب البعيد، وقد يقوم العديد من المستخدمين بتشغيله مرة واحدة.

يحتوي Sun's Java 1.4 على الحجج التالية للتحكم في حجم الذاكرة:

-XMSN حدد الحجم الأولي، في البايتات، من تجمع تخصيص الذاكرة. يجب أن تكون هذه القيمة متعددة 1024 أكبر من 1 ميغابايت. إلحاق الرسالة K أو K للإشارة إلى KiloBytes أو M أو M للإشارة إلى ميغابايت. القيمة الافتراضية هي 2MB. أمثلة:

           -Xms6291456
           -Xms6144k
           -Xms6m

حدد -xmxn الحد الأقصى للحجم، في البايتات، من تجمع تخصيص الذاكرة. يجب أن تكون هذه القيمة متعددة 1024 أكبر من 2 ميغابايت. إلحاق الرسالة K أو K للإشارة إلى KiloBytes أو M أو M للإشارة إلى ميغابايت. القيمة الافتراضية هي 64 ميجابايت. أمثلة:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html.

جافا 5 و 6 لديها المزيد. يرى http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp.

لا، لا يمكنك تكوين مبلغ الذاكرة اللازمة بواسطة VM. ومع ذلك، لاحظ أن هذه هي الذاكرة الظاهرية، وليس المقيمين، لذلك يبقى فقط هناك دون ضرر إذا لم تستخدم فعلا.

من النظير، يمكنك تجربة بعض JVM الأخرى ثم Sun One، مع بصمة أصغر ذاكرة، لكن لا يمكنني تقديم المشورة هنا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top