القضية والتحليل
خلفية المشكلة
في Tomcat ، يوجد الرمز التالي داخل WebApp ، مما سيؤدي إلى تسرب WebappClassLoader ولا يمكن إعادة تدويره.
الفئة العامة mycounter {private int count = 0 ؛ زيادة الفراغ العام () {count ++ ؛ } public int getCount () {return count ؛ }} الفئة العامة myThreadlocal تمتد threadlocal <myCounter> {} الفئة العامة elekingservlet تمتد httpservlet {private static mythreadlocal mythreadlocal = new MyThreadlocal () ؛ DOGED VOID DOGET (طلب httpservletrequest ، استجابة httpservletresponse) يلقي servleTexception ، ioException {mycounter counter = mythreadlocal.get () ؛ if (counter == null) {counter = new mycounter () ؛ mythreadlocal.set (عداد) ؛ } response.getWriter (). println ("خدم الخيط الحالي هذا servlet" + counter.getCount () + "Times") ؛ counter.increment () ؛ }} في الكود أعلاه ، طالما يتم استدعاء LeakingServlet مرة واحدة ولا يتم إيقاف مؤشر الترابط الذي ينفذه ، سيحدث تسرب WebappClassLoader . في كل مرة تقوم فيها reload التطبيق ، سيكون لديك مثيل WebappClassLoader إضافي ، والذي سيؤدي في النهاية إلى PermGen OutOfMemoryException .
حل المشكلة
الآن دعونا نفكر في الأمر: لماذا تسبب الفئة الفرعية ThreadLocal أعلاه تسرب الذاكرة؟
WebAppClassloader
بادئ ذي بدء ، نحن بحاجة إلى معرفة ما هو بحق الجحيم WebappClassLoader ؟
بالنسبة لتطبيقات الويب التي تعمل في حاويات Java EE ، يختلف تنفيذ لوادر الفئة عن تطبيقات Java العامة. كما سيتم تنفيذ حاويات الويب المختلفة بشكل مختلف. في Apache Tomcat ، يحتوي كل تطبيق ويب على مثيل Loader فئة مقابلة. يستخدم محمل الفئة هذا أيضًا وضع الوكيل ، والفرق هو أنه يحاول أولاً تحميل فئة معينة ، ثم وكيلًا إلى محمل الفئة الأصل إذا كان لا يمكن العثور عليه. هذا هو عكس ترتيب اللوادر العامة للطبقة العامة. هذه ممارسة موصى بها في مواصفات Java Servlet ، والغرض منها هو جعل فئات تطبيق الويب الخاصة بأولوية أعلى من تلك التي توفرها حاوية الويب. الاستثناء من هذا النمط الوكيل هو أن الفصول في مكتبة Java Core ليست ضمن نطاق البحث. هذا هو أيضا لضمان نوع سلامة مكتبة جافا الأساسية.
بمعنى آخر ، WebappClassLoader هو محمل فئة مخصص لـ Tomcat لتحميل WebApps. يختلف محمل الفئة لكل WebApp. هذا هو عزل الفصول التي يتم تحميلها بواسطة تطبيقات مختلفة.
إذن ما علاقة ميزات WebappClassLoader بتسرب الذاكرة؟ إنه ليس مرئيًا بعد ، ولكنها ميزة مهمة للغاية تستحق اهتمامنا: كل WebApp له WebappClassLoader الخاص به ، والذي يختلف عن محمل فئة Java Core.
نحن نعلم أن تسرب WebappClassLoader يجب أن يكون لأنه يتم الإشارة إليه بقوة من قبل كائنات أخرى ، حتى نتمكن من محاولة رسم مخطط العلاقة المرجعية الخاصة بهم. إلخ! ما الذي يعمل به محمل الفصل الدراسي بالضبط؟ لماذا يجبر على الاقتباس؟
دورة الحياة الطبقية ودراسة الطبقة
لحل المشكلة أعلاه ، علينا أن ندرس العلاقة بين دورة حياة الفصل والدراسة.
الشيء الرئيسي المتعلق بحالتنا هو إلغاء التثبيت:
بعد استخدام الفصل ، إذا تم استيفاء الموقف التالي ، فسيتم إلغاء تثبيت الفصل:
1. تم إعادة تدوير جميع حالات هذه الفئة ، أي توجد حالات من هذه الفئة في كومة Java.
2. تم إعادة تدوير تحميل ClassLoader .
3. لا يتم الرجوع إلى كائن java.lang.Class المقابل لهذا الفئة في أي مكان ، ولا توجد طريقة للوصول إلى الفصل من خلال التفكير في أي مكان.
إذا تم استيفاء جميع الشروط الثلاثة المذكورة أعلاه ، فسيقوم JVM بإلغاء تثبيت الفصل أثناء جمع القمامة في منطقة الطريقة. تتمثل عملية إلغاء التثبيت في الفصل في في الواقع مسح معلومات الفصل في منطقة الطريقة ، وتنتهي دورة حياة فئة Java بأكملها.
لن يتم إلغاء تثبيت الفصول التي تم تحميلها بواسطة تحميل الفئة التي تأتي مع جهاز Java Virtual أثناء دورة حياة الجهاز الظاهري. تشمل اللوادر الفئة التي تأتي مع الأجهزة الافتراضية Java لوادر فئة الجذر ، ووادر فئة الامتداد ووادر فئة النظام. يشير جهاز Java Virtual نفسه دائمًا إلى محملات الفئة هذه ، وتشير محملات الفئة هذه دائمًا إلى كائنات الفئة من الفئة التي يتم تحميلها ، لذلك يمكن الوصول إلى كائنات الفئة هذه دائمًا.
يمكن تفريغ الفئات التي تم تحميلها بواسطة لوادر الفئة المعرفة من قبل المستخدم.
لاحظ الجملة أعلاه. إذا كانت WebappClassLoader تتسرب ، فهذا يعني أنه لا يمكن إلغاء تثبيت الفئات التي يتم تحميلها ، مما يفسر سبب تسبب الرمز المذكور أعلاه PermGen OutOfMemoryException .
انظر إلى الصورة أدناه في النقطة الرئيسية
يمكننا أن نجد أن كائن تحميل الفئة يرتبط بشكل ثنائي الاتجاه بكائن الفئة الذي يتم تحميله. هذا يعني أن كائن الفصل قد يكون السبب في المرجع القسري إلى WebappClassLoader ، مما تسبب في تسربه.
اقتباس الرسم البياني
بعد فهم العلاقة بين اللودر الطبقي ودورة حياة الفصل ، يمكننا البدء في رسم مخطط علاقة مرجعية. ( LeakingServlet.class و myThreadLocal في الشكل ليست صارمة في الرسم المرجعي ، والتعبير بشكل أساسي عن أن myThreadLocal هو متغير فئة)
أدناه ، نقوم بتحليل سبب تسرب WebappClassLoader استنادًا إلى الرسم البياني أعلاه.
1. حاصل MyThreadLocal LeakingServlet static مما يؤدي إلى دورة حياة myThreadLocal طالما كانت دورة حياة فئة LeakingServlet . هذا يعني أنه لن يتم إعادة تدوير myThreadLocal ، وأن المراجع الضعيفة عديمة الفائدة ، لذلك لا يمكن للموضوع الحالي مسح المرجع القوي للعداد من خلال تدابير حماية ThreadLocalMap .
2. سلسلة مرجعية قوية: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader ، مما تسبب في تسرب WebappClassLoader .
لخص
يصعب اكتشاف تسرب الذاكرة وغالبًا ما يكون سببها العديد من الأسباب. أصبح Threadlocal زائرًا متكررًا لتسربات الذاكرة نظرًا لدورة حياته المرتبطة بالخيوط ، وسيؤدي القليل من الإهمال إلى كارثة. هذه المقالة هي مجرد تحليل لحالة معينة. إذا كنت تستطيع استخدام هذا لتطبيقه على حالات أخرى ، فسيكون ذلك ممتازًا. آمل أن تكون هذه المقالة مفيدة للجميع.