عند تحديد موقع مشكلات أداء JVM ، قد تواجه تسرب الذاكرة وتتسبب في jvm OutofMemory. إذا تم تعيين المعلمة RELOADABLE = "TRUE" عند استخدام حاوية Tomcat ، فيمكنك أيضًا مواجهة تدفق الذاكرة عند نشر التطبيقات بشكل متكرر. يتمثل مبدأ النشر الساخن لـ TomCat في اكتشاف أن الملفات الموجودة في ويب/فئات أو دليل الويب/LIB يتم تغييرها وسيتم إيقاف التطبيق أولاً ثم بدء تشغيله. نظرًا لأن Tomcat يعين جهاز WebAppClassloader لكل تطبيق افتراضيًا ، فإن مبدأ الاستبدال الساخن هو إنشاء جهاز تحميل فئة جديد لتحميل الفصل. نظرًا لأن تفرد الفئة في JVM يتم تحديده من خلال ملف الفئة الخاص به وموقد الفئة الخاص به ، فإن إعادة تحميل الفصل يمكن أن يحقق الغرض من الاستبدال الساخن. عندما يكون عدد عمليات النشر الساخنة أكثر تواتراً ، فإنه سيؤدي إلى المزيد من الفصول التي يتم تحميلها بواسطة JVM. إذا لم يتم تفريغ الفئة السابقة في الوقت المناسب لسبب ما (مثل تسرب الذاكرة) ، فقد يؤدي ذلك إلى جيل دائم أو Metaspace OutofMemory. تقدم هذه المقالة باختصار السيناريو الذي يؤدي فيه Threadlocal و Classloader إلى تسرب الذاكرة وتتفوق في نهاية المطاف من خلال العرض التوضيحي.
إلغاء تثبيت الفصل
بعد استخدام الفصل ، إذا تم استيفاء الموقف التالي ، فسيتم إلغاء تثبيته:
1. تم إعادة تدوير جميع مثيلات هذه الفئة في الكومة ، أي كائنات مثيل لهذه الفئة غير موجودة في الكومة.
2. تم إعادة تدوير تحميل classloader.
3. لا يمكن الرجوع إلى كائن الفئة المقابل لهذا الفئة في أي مكان ، ولا يمكن الوصول إلى كائن الفئة من خلال التفكير.
إذا استوفى الفصل شروط إلغاء التثبيت ، فإن JVM يقوم بإلغاء تثبيت الفصل عندما يكون في GC ، أي ، يمسح معلومات الفصل في منطقة الطريقة.
مقدمة المشهد
في المقالة السابقة ، قدمت مبدأ Threadlocal. كل موضوع لديه threadlocalmap. إذا كانت دورة حياة الخيط طويلة نسبيًا ، فقد لا يتم إعادة تدوير الإدخال في threadlocalmap. لطالما تمت الإشارة بقوة إلى كائن threadlocal بقوة بواسطة مؤشر الترابط. نظرًا لأن كائن المثيل سيعقد مرجع كائن الفئة ، فإن كائن الفئة سيحتفظ بمرجع جهاز تحميل الفئة الذي يقوم بتحميله ، مما سيؤدي إلى تفريغ الفئة. عندما يكون هناك فئات كافية محملة ، قد يحدث جيل دائم أو فيضل الذاكرة Metaspace. إذا كان للفئة كائنات كبيرة ، مثل صفيف البايت الأكبر ، فسوف يتسبب ذلك في تدفق الذاكرة لمنطقة كومة Java.
مقدمة رمز المصدر
هنا فئة داخلية داخلية. تحتوي الفئة الداخلية على كائن ثابت ثابت ، والذي يستخدم بشكل أساسي لجعل مؤشرات الترابط يحمل إشارات قوية إلى الفئة الداخلية ، بحيث لا يمكن إعادة تدوير الفئة الداخلية. يتم تعريف محمل فئة مخصصة لتحميل الفئة الداخلية ، كما هو موضح أدناه:
MemoryLeak ClasselyLeak {public static void main (string [] args) {// لأن مؤشر الترابط يعمل طوال الوقت ، فقد تمت الإشارة إلى الكائن الداخلي في ThreadLocalmAP بقوة من قِبل مؤشر ترابط كائن جديد (new RunNable () ("load1" ، memoryleak.class.getclassloader () ، "com.ezlippi.memoryleak $" ، "com.ezlippi.memoryleak $ 1") GC للمعالجة المرجعية innerclass = null ؛ } // من أجل الوصول إلى منطقة الكومة بشكل أسرع من الفئة الثابتة العامة {private byte [] mb = new byte [1024 * 1024] ؛ static threadlocal <inner> threadlocal = new threadlocal <inner> () {Override محمي initialValue () {return new inner () ؛ }} ؛ // لاستدعاء threadlocal.get () لتهيئة كائن داخلي ثابت {threadlocal.get () ؛ } public inner () {}} // رمز المصدر حذف الفئة الثابتة الخاصة customClassloader يمتد classloader {}تفوق ذاكرة منطقة الكومة
من أجل تشغيل تدفق ذاكرة الكومة ، قمت بإعداد صفيف بايت 1 ميجابايت في الفئة الداخلية ، وفي الوقت نفسه ، يجب أن أتصل بـ threadlocal.get () في الكتلة الثابتة. ستؤدي المكالمة فقط إلى تشغيل initialValue () لتهيئة كائن داخلي ، وإلا سأقوم فقط بإنشاء كائن مؤشر ترابط فارغ ، ولا توجد بيانات في threadlocalmap.
معلمات JVM هي كما يلي:
-xms100m -xmx100m -xx:+usparnewgc -xx:+useconcmarksweepgc -xx:+printgcdetails -xx:+printheapatgc -xx:+printclasshist -xx:+heapdumpontofmyror
بعد آخر 814 عملية إعدام ، تفيض ذاكرة منطقة JVM ، كما هو موضح أدناه:
java.lang.outofmemoryerror: java Heap toppumping heap to java_pid11824.hprof ... ملف تفريغ كومة تم إنشاؤه [1006661202 بايت في 1.501 secs] Heap Par New Generation Total 30720k ، use 30389k [0x000000ff9c0000000000000000000000000000000000000000000000000000) 99 ٪ مستعملة [0x0000000000F9C000000 ، 0x0000000000FB6AD450 ، 0x000000000000FB6B0000) [0x00000000000000FBA00000 ، 0x0000000000FBD50000000) إجمالي مارك ماركت SWEEP 68288K ، المستخدمة 67600K [0x00000000000FBD500000 ، 0x000000000000000 ، 0x0000000100000) مستقلة 37770K ، القدرة 5134K ، الالتزام 5248K ، Mreapted. تستخدم مساحة الفئة 474 كيلو بايت ، 578 كيلو ، ملتزمة 640 كيلو ، محفوظة 1048576kexception في الموضوع "thread-0" sun.reflect.nativeConstructorAccessorImpl.NewInstance0 (الطريقة الأصلية) في sun.reflect.nativeconstructorAcsorImpl.NewInstance (مصدر غير معروف) في java.lang.refultructor (intarner java.lang.reflect.constructor.newinstance (مصدر غير معروف) المصدر) في java.lang.class.newinstance (مصدر غير معروف) في com.ezlippi.memoryleak $ 1.run (memoryleak.java:20) في java.lang.thread.run (مصدر غير معروف)
يمكنك أن ترى أن JVM ليس لديه ذكرى لإنشاء كائن داخلي جديد ، لأن منطقة الكومة تخزن العديد من المصفوفات البايت 1 ميجابايت. هنا قمت بطباعة الرسم البياني للفئة (الشكل التالي هو مشهد بحجم كومة يبلغ 1024 مترًا) ، مما يحذف بعض الطبقات غير المهمة. يمكن ملاحظة أنه يتم إنشاء صفيف البايت 855 مترًا من المساحة ، و 814 حالة من com.ezlippi.MemoryLeak $ CustomClassloader ، والتي تتطابق بشكل أساسي مع حجم صفيف البايت:
NUM #INSTACASES #BYTES اسم------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- com.ezlippi.memoryleak $ customClassloader 12: 820 53088 [ljava.util.hashtable $ intpl ؛ 15: 817 39216 java.util.hashtable 16: 915 36600 java.lang.ref.softreference 17: 543 34752 java.net.url 18: 697 33456 java.nio.heaparfer 19: 817 32680 java.securand java.util.treemap $ intring 21: 928 29696 java.util.hashtable $ unter 22: 1802 28832
Metaspace overflow
من أجل جعل فيضل metaspace ، يجب أن تقلل من مساحة metAspace قليلاً ، وتحميل فصول كافية قبل أن يتفوق الكومة. لذلك ، قمت بتعديل معلمات JVM وضبط حجم صفيف البايت إلى 1 كيلو بايت ، كما هو موضح أدناه:
بايت خاص [] kb = new byte [1024] ؛ -xms100m -xmx100m -xx:+useparnewgc -xx:+Useconcmarksweepgc -xx:+printgcdetails -xx:+printheapatgc -xx:+printclasshistogram -xx: met
من سجل GC ، يمكننا أن نرى أنه عندما يصل Meraspace إلى عتبة GC (أي حجم تكوين MaxMeTaspAdize) سيتم تشغيل FullGC:
java.lang.outofmemoryerror: metaspace << no trace trace متاح >> {heap قبل Invocations gc = 20 (full 20): Par New Generation Total 30720k ، use 0k [0x000000000F9C000000 ، 0x000000000000000) Eden 27328K ، 0x0000000000F9C00000 ، 0x0000000F9C00000 ، 0x0000000F9C00000 ، 0X0000000FB6B0000) [0x00000000000000FBA00000 ، 0x00000000000FBD500000) إلى الفضاء 3392K ، 0 ٪ مستخدمة [0x0000000000000000000 ، 0x000000010000000000) Metasp use 1806k ، Traction 1988k ، ملتزم 2048K ، محفوظ 10567k فئة المستخدمة 202K ، Crufft 384K ، Crufft 384K 1048576K [GC Full (STADATA GC THERSHOLD) [انتهى CMSPROCEST مع رمز الخروج 1من المثال أعلاه ، يمكننا أن نرى أنه إذا تم استخدام محمل الفئة و threadlocal بشكل غير صحيح ، فسيؤدي ذلك بالفعل إلى تسرب الذاكرة. رمز المصدر الكامل في جيثب