كيفية تحليل تفريغ الخيط

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

هل تريد معرفة كيفية تحليل تفريغ سلسلة المحادثات؟ اذهب تحت القطة لمعرفة المزيد حول كيفية الحصول على تفريغ الخيط في Java وما يجب القيام به في وقت لاحق.

معظم تطبيقات Java الحديثة متعددة مؤشرات الترابط. يمكن أن يؤدي تعدد الوظائف إلى توسيع وظائف التطبيق بشكل كبير ، وفي نفس الوقت يقدم تعقيدًا كبيرًا.

في تطبيق أحادي الخيوط ، يمكن استخدام جميع الموارد (الذاكرة المشتركة ، عمليات الإدخال / الإخراج ، وما إلى ذلك) بدون مزامنة ، لأن في أي وقت معين ، يستخدم مؤشر ترابط واحد المورد.

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

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

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

المصطلحات الأساسية


للوهلة الأولى ، قد تبدو مقالب خيط جافا مثل "حرف صيني" ، والمفاهيم التالية هي المفتاح لفهمها. بشكل عام ، دعنا نكرر المصطلحات الأساسية للتعددية ، التي سنستخدمها لتحليل مقالب.

  • مؤشر الترابط أو مؤشر الترابط هو وحدة تعدد مؤشرات منفصلة يتم إدارتها بواسطة Java Virtual Machine (JVM). تتوافق سلاسل عمليات JVM مع سلاسل العمليات في نظام التشغيل - سلاسل العمليات الأصلية ، التي تقوم بتنفيذ آلية تنفيذ الكود.

    كل خيط لديه معرف واسم فريد. يمكن أن تكون الجداول "شياطين" و "ليست شياطين".

    ينتهي البرنامج عندما تنتهي جميع سلاسل الرسائل غير الخفية أو يتم استدعاء أسلوب Runtime.exit . "الشياطين" العاملة لا تؤثر على إكمال البرنامج. على سبيل المثال JVM تنتظر الانتهاء من جميع "غير الشياطين" وإغلاقها ؛ فهم لا ينتبهون إلى "غير الشياطين".

    لمزيد من المعلومات ، راجع وثائق فئة مؤشر الترابط .
    قد يكون الدفق في إحدى الحالات التالية:

    • خيط حي أو "مباشر" - خيط يقوم ببعض الأعمال (الحالة الطبيعية).
    • مؤشر الترابط المحظور أو "المحظور" - مؤشر ترابط حاول إدخال القسم المتزامن (متزامن) ، لكن مؤشر ترابط آخر تمكن بالفعل من إدخال هذا الحظر أولاً ، ويتم حظر كافة مؤشرات الترابط التالية التي تحاول إدخال نفس الكتلة.
    • انتظار مؤشر الترابط أو "انتظار" - مؤشر ترابط يسمى أسلوب الانتظار (ربما مع انتهاء المهلة) وينتظر الآن طريقة أخرى لتنفيذ إعلام أو nonifyAll على نفس الكائن.

      يرجى ملاحظة أن سلسلة المحادثات لا تعتبر "قيد الانتظار" إذا كانت تسمى الانتظار مع انتهاء المهلة وقد انتهت صلاحية هذه المهلة.
    • خيط النوم أو "النوم" - خيط ليس قيد التشغيل حاليًا ، لأنه نفذت طريقة Thread.sleep (تشير إلى مدة "النوم").
  • جهاز المراقبة عبارة عن آلية يستخدمها JVM لتوفير وصول متعدد الخيوط لكائن واحد. تبدأ الآلية باستخدام الكلمة الأساسية المتزامنة الخاصة.

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

    راجع قسم التزامن (17.1) من مواصفات Java Langauge (JLS) لمزيد من المعلومات .
  • حالة توقف تام هي حالة يقوم فيها مؤشر ترابط ، مثل A ، بحظر أحد الموارد ، ويحتاج إلى مورد آخر تم حظره بواسطة مؤشر ترابط آخر ، على سبيل المثال B. لا يقوم Stream B بإصدار هذا المورد ، لأنه لإكمال عملية معينة ، يحتاج إلى مورد تم حظره بواسطة مؤشر الترابط A. وتبين أن مؤشر الترابط A ينتظر إلغاء تأمين المورد بواسطة مؤشر الترابط B ، والذي ينتظر أن يتم فتح مورد آخر بواسطة مؤشر الترابط A. ، وبالتالي ، فإن سلاسل الرسائل في انتظار بعضها البعض. ونتيجة لذلك ، يتم تعليق البرنامج بالكامل وانتظار ظهور سلاسل الرسائل بطريقة ما لإلغاء القفل والاستمرار في العمل. يمكن أن يكون هناك العديد من الخيوط في طريق مسدود. تُعرف هذه المشكلة جيدًا باسم "مشكلة تناول الطعام للفلاسفة" .


  • Livelock هو وضع حيث يُجبر مؤشر الترابط A مؤشر الترابط B على تنفيذ بعض الإجراءات ، مما يؤدي بدوره إلى مؤشر الترابط A لتنفيذ الإجراء الأولي ، والذي يتسبب مرة أخرى في عمل مؤشر الترابط B. يمكن تخيل ذلك ككلب يركض خلف ذيله. على غرار Deadlock ، في حالة Livelock ، لا يحرز البرنامج أي تقدم ، أي لا يقوم بإجراء مفيد ، ومع ذلك ، في هذه الحالة ، لا يتم حظر مؤشرات الترابط.

المصطلحات المقدمة ليست شاملة لوصف عالم multithreading ، ولكن هذا يكفي لبدء تحليل مقالب الخيط.

يمكن العثور على معلومات أكثر تفصيلاً في هذه المصادر:

القسم 17 من JLS وجافا التزامن في الممارسة

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

إنشاء برنامج عينة


قبل إنشاء تفريغ سلسلة المحادثات ، نحتاج إلى تطوير تطبيق Java. التقليدية "مرحبا ، العالم!" بسيطة للغاية لغرضنا ، وقد يكون تفريغ التطبيق متوسط ​​الحجم معقدًا للغاية بحيث لا يمكن إثباته. بناءً على ذلك ، سننشئ تطبيقًا بسيطًا إلى حد ما يتم فيه إنشاء خيطين. وتصل الخيوط إلى طريق مسدود:

public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceB, resourceA)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized(firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized(secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

ينشئ هذا البرنامج موردين: ResourceA و ResourceB ، ويبدأ خيطين: threadLockingResourceAFirst و threadLockingResourceBFirst ، والتي تمنع موارد بعضها البعض.

سبب الجمود هو "عبر" حجب الموارد عن طريق المواضيع.

سبب حدوث الجمود هو محاولة الاستيلاء على الموارد "بشكل متبادل" ، أي threadLockingResourceAFirst يلتقط المورد ResourceA ، threadLockingResourceBFirst مؤشر ترابط الموارد B المورد. بعد ذلك ، يحاول threadLockingResourceAFirst ، دون تحرير مورده ، التقاط المواردB ، ويحاول threadLockingResourceBFirst التقاط الموردA. نتيجة لذلك ، يتم حظر مؤشرات الترابط. تمت إضافة تأخير 1s لضمان الحظر. مؤشرات الترابط تنتظر إصدار الموارد الضرورية ، ولكن هذا لن يحدث أبدًا.

سيكون ناتج البرنامج على هذا النحو (ستكون الأرقام بعد java.lang.Object @ مختلفة لكل عملية إطلاق):

 Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009 

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

جيل تفريغ الخيط


من الناحية العملية ، قد يتلف برنامج Java أثناء إنشاء تفريغ مؤشر ترابط. ومع ذلك ، في بعض الحالات (على سبيل المثال ، في حالة الجمود) ، لا ينتهي البرنامج ولا يتم إنشاء تفريغ مؤشر الترابط ، يتم تعليقه فقط. لإنشاء تفريغ لهذه البرامج المعلقة ، تحتاج أولاً إلى معرفة معرف عملية البرنامج ، أي معرف العملية (PID). للقيام بذلك ، يمكنك استخدام الأداة المساعدة JVM Process Status (JPS) ، والتي ، بدءًا من الإصدار 7 ، هي جزء من Java Development Kit (JDK). للعثور على عملية PID لبرنامجنا المعلق ، نقوم ببساطة بتنفيذ jps في الطرفية (Windows أو Linux):

 $ jps 11568 DeadlockProgram 15584 Jps 15636 

العمود الأول هو معرف الجهاز الظاهري المحلي (معرف الجهاز الظاهري المحلي ، أي lvmid) لعملية Java قيد التشغيل. في سياق JVM المحلي ، يشير lvmid إلى PID لعملية Java.

وتجدر الإشارة إلى أن هذه القيمة من المحتمل أن تختلف عن القيمة المذكورة أعلاه. العمود الثاني هو اسم التطبيق ، والذي يمكن أن يشير إلى اسم الفئة الرئيسية أو ملف jar أو يساوي "Unknown". كل هذا يتوقف على كيفية تشغيل التطبيق.

في حالتنا ، اسم التطبيق DeadlockProgram هو اسم الفئات الرئيسية التي تم إطلاقها عند بدء البرنامج. في المثال أعلاه PID من البرنامج 11568 ، هذه المعلومات كافية لإنشاء تفريغ مؤشر ترابط. لإنشاء التفريغ ، سنستخدم الأداة المساعدة jstack ، والتي تعد جزءًا من JDK ، بدءًا من الإصدار 7. للحصول على التفريغ ، سنقوم بتمرير PID من برنامجنا إلى jstack وتحديد علامة ll (إنشاء قائمة طويلة). سيتم إعادة توجيه إخراج الأداة المساعدة إلى ملف نصي ، أي thread_dump.txt:

 jstack -l 11568 > thread_dump.txt 

يحتوي ملف thread_dump.txt الناتج على تفريغ مؤشر الترابط لبرنامجنا المعلق ويحتوي على معلومات مهمة لتشخيص أسباب حالة الجمود.

إذا تم استخدام JDK حتى الإصدار 7 ، فعندئذٍ لإنشاء ملف تفريغ ، يمكنك استخدام الأداة المساعدة Linux - قتل باستخدام علامة -3. سيؤدي الاتصال بقتل -3 إلى إرسال البرنامج إشارة SIGQUIT.

في حالتنا ، ستكون المكالمة على النحو التالي:

 kill -3 11568 

تحليل تفريغ موضوع بسيط


عند فتح ملف thread_dump.txt ، سنرى شيئًا مثل ما يلي:

 2018-06-19 16:44:44
 تفريغ مؤشر ترابط كامل Java HotSpot (TM) 64-bit Server VM (10.0.1 + 10 وضع مختلط):
 مواضيع SMR فئة المواضيع:
 _java_thread_list = 0x00000250e5488a00 ، الطول = 13 ، العناصر = {
 0x00000250e4979000 ، 0x00000250e4982800 ، 0x00000250e52f2800 ، 0x00000250e4992800 ،
 0x00000250e4995800 ، 0x00000250e49a5800 ، 0x00000250e49ae800 ، 0x00000250e5324000 ،
 0x00000250e54cd800 ، 0x00000250e54cf000 ، 0x00000250e54d1800 ، 0x00000250e54d2000 ،
 0x00000250e54d0800
 }}
 "معالج مرجعي" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 في حالة الانتظار [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     على java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/ الطريقة الأصلية)
     على java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     على java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     على java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Finalizer" # 3 daemon prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 في Object.wait () [0x000000b82aaff000]
    java.lang.Thread.State: WAITING (on object monitor)
     على java.lang.Object.wait (java.base@10.0.1/ الطريقة الأصلية)
     - الانتظار على <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
     على java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - في انتظار إعادة القفل أثناء الانتظار () <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
     على java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 172)
     على java.lang.ref.Finalizer $ FinalizerThread.run (java.base@10.0.1/Finalizer.java: 216)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "مرسل الإشارة" # 4 البرنامج الخفي = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184 قابل للتشغيل [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Attach Listener" # 5 daemon prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624 انتظار شرط [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "C2 CompilerThread0" # 6 daemon prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198 قيد الانتظار [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    لا توجد مهمة ترجمة
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "C2 CompilerThread1" # 7 daemon prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98 قيد الانتظار [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    لا توجد مهمة ترجمة
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "C1 CompilerThread2" # 8 daemon prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84 قيد الانتظار [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    لا توجد مهمة ترجمة
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Sweeper thread" # 9 daemon prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0 قابل للتشغيل [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Service Thread" # 10 daemon prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c قابل للتشغيل [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Common-Cleaner" # 11 daemon prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 في Object.wait () [0x000000b82b2fe000]
    java.lang.Thread.State: TIMED_WAITING (على مراقبة الكائن)
     على java.lang.Object.wait (java.base@10.0.1/ الطريقة الأصلية)
     - الانتظار على <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     على java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - في انتظار إعادة القفل أثناء الانتظار () <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     على jdk.internal.ref.CleanerImpl.run (java.base@10.0.1/CleanerImpl.java: 148)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
     على jdk.internal.misc.InnocuousThread.run (java.base@10.0.1/InnocuousThread.java: 134)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec انتظار إدخال الشاشة [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (على مراقبة الكائن)
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465b0> (كائن java.lang.Object)
     - مقفل <0x00000000894465a0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "Thread-1" # 13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c انتظار إدخال الشاشة [0x000000b82b5ff000]
    java.lang.Thread.State: BLOCKED (على مراقبة الكائن)
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465a0> (كائن java.lang.Object)
     - مؤمن <0x00000000894465b0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "DestroyJavaVM" # 14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c في حالة الانتظار [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء
 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 قابل للتشغيل  
 "G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 قابل للتشغيل  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 قابل للتشغيل  
 "G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c قابل للتشغيل  
 "G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 قابل للتشغيل  
 "G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 قابل للتشغيل  
 "G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 قابل للتشغيل  
 "VM Periodic Task Thread" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 قيد الانتظار  
 المراجع العالمية لـ JNI: 2
 العثور على طريق مسدود على مستوى جافا:
 ===============================
 "مؤشر ترابط 0":
   في انتظار قفل الشاشة 0x00000250e4982480 (الكائن 0x00000000894465b0 ، java.lang.Object) ،
   الذي عقده "Thread-1"
 "الخيط 1":
   في انتظار قفل الشاشة 0x00000250e4982380 (الكائن 0x00000000894465a0 ، a java.lang.Object) ،
   الذي عقده "Thread-0"
 معلومات مكدس Java للخيوط المذكورة أعلاه:
 =================================================== =
 "مؤشر ترابط 0":
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465b0> (كائن java.lang.Object)
     - مقفل <0x00000000894465a0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "الخيط 1":
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465a0> (كائن java.lang.Object)
     - مؤمن <0x00000000894465b0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 تم العثور على حالة توقف تام 1.

معلومات تمهيدية


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

يشير السطر الأول إلى الوقت الذي تم فيه تشكيل التفريغ ، والثاني - معلومات تشخيصية عن JVM ، حيث تم تلقي التفريغ:

 2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode): 

لا توجد معلومات تدفق في هذا القسم. هنا يتم تعيين السياق العام للنظام الذي تم فيه تجميع التفريغ.

معلومات التدفق العام


يوفر القسم التالي معلومات حول سلاسل العمليات التي كانت تعمل في النظام في وقت جمع التفريغ:

 مواضيع SMR فئة المواضيع:
 _java_thread_list = 0x00000250e5488a00 ، الطول = 13 ، العناصر = {
 0x00000250e4979000 ، 0x00000250e4982800 ، 0x00000250e52f2800 ، 0x00000250e4992800 ،
 0x00000250e4995800 ، 0x00000250e49a5800 ، 0x00000250e49ae800 ، 0x00000250e5324000 ،
 0x00000250e54cd800 ، 0x00000250e54cf000 ، 0x00000250e54d1800 ، 0x00000250e54d2000 ،
 0x00000250e54d0800
 }}

يسرد القسم التالي:

معلومات استعادة الذاكرة الآمنة (SMR)

يحتوي على معلومات حول سلاسل الرسائل خارج JVM ، أي هذه ليست خيوط الجهاز الظاهري أو خيوط جمع القمامة. إذا نظرت إلى عناوين هذه المواضيع ، فستلاحظ أنها تتوافق مع قيمة tid - العنوان "الطبيعي ، والحديدي" (الأصلي) في نظام التشغيل ، وليس معرف مؤشر الترابط.

تُستخدم علامات الحذف لإخفاء المعلومات الزائدة:

 "معالج مرجعي" # 2 ... tid = 0x00000250e4979000 ...
 "Finalizer" # 3 ... tid = 0x00000250e4982800 ...
 "مرسل الإشارة" # 4 ... tid = 0x00000250e52f2800 ...
 "إرفاق مستمع" # 5 ... tid = 0x00000250e4992800 ...
 "C2 CompilerThread0" # 6 ... tid = 0x00000250e4995800 ...
 "C2 CompilerThread1" # 7 ... tid = 0x00000250e49a5800 ...
 "C1 CompilerThread2" # 8 ... tid = 0x00000250e49ae800 ...
 "مؤشر ترابط الكاسح" # 9 ... tid = 0x00000250e5324000 ...
 "مؤشر ترابط الخدمة" # 10 ... tid = 0x00000250e54cd800 ...
 "Common-Cleaner" # 11 ... tid = 0x00000250e54cf000 ...
 "Thread-0" # 12 ... tid = 0x00000250e54d1800 ...
 "Thread-1" # 13 ... tid = 0x00000250e54d2000 ...
 "DestroyJavaVM" # 14 ... tid = 0x00000250e54d0800 ...

تيارات


مباشرة بعد كتلة SMR قائمة المواضيع. الخيط الأول في قائمتنا هو معالج المرجع:

 "معالج مرجعي" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 في حالة الانتظار [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     على java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/ الطريقة الأصلية)
     على java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     على java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     على java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء

وصف موجز للتيار


يوفر السطر الأول لكل خيط وصفًا عامًا. يحتوي الوصف على العناصر التالية:
القسممثالالوصف
الاسم"معالج المرجع"اسم دفق قابل للقراءة البشرية. يمكن تحديد الاسم عن طريق استدعاء طريقة setName لكائن Thread . وتلقي مكالمة عبر getName
ID# 2معرف فريد يتم تعيينه لكل كائن في فئة سلسلة العمليات. يتم إنشاء معرف لمؤشرات الترابط في النظام. القيمة الأولية هي 1. يتم تعيين معرّف خاص لكل مؤشر ترابط تم إنشاؤه حديثًا ، وقد تم زيادته مسبقًا بمقدار 1. يمكن الحصول على خاصية مؤشر الترابط للقراءة فقط باستخدام وظيفة getId لكائن من فئة مؤشر الترابط .
حالة الشيطانالشيطانالعلم هو علامة على أن الخيط شيطان. إذا كان شيطانًا ، فسيتم تعيين العلم. على سبيل المثال ، مؤشر الترابط -0 ليس خفيًا.
الأولويةprio = 10الأولوية العددية لتيار Java. لاحظ أن هذه الأولوية لا تتوافق بالضرورة مع أولوية مؤشر الترابط المرتبط في نظام التشغيل. لتعيين الأولوية ، يمكنك
استخدم طريقة setPriority لكائن من سلسلة Thread ، وللحصول على
طريقة getPriority .
أولوية مؤشر ترابط نظام التشغيلos_prio = 2مؤشر ترابط ذو أولوية في نظام التشغيل. قد تختلف هذه الأولوية عن تلك المعينة لمؤشر ترابط Java المرتبط.
العنوانتيد = 0x00000250e4979000عنوان دفق جافا. هذا العنوان هو مؤشر للكائن الأصلي لـ Java Native Interface (JNI) لفئة Thread (كائن سلسلة C ++ متصل بسلسلة رسائل Java عبر JNI). يتم الحصول على هذه القيمة عن طريق صب مؤشر لهذا
(كائن C ++ المرتبط بسلسلة رسائل Java هذه) إلى عدد صحيح. انظر
السطر 879 في نقطة الاتصال / المشاركة / وقت التشغيل / thread.cpp :
 st-> print ("tid =" INTPTR_FORMAT ""، p2i (this)) ؛

على الرغم من أن المفتاح لهذا الكائن ( tid ) قد يبدو مثل معرف الدفق ،
في الواقع ، هذا هو عنوان كائن متصل JNI C ++ Thread ، وهذه ليست القيمة التي
إرجاع طريقة getId لمؤشر ترابط Java.
معرف مؤشر ترابط نظام التشغيلnid = 0x3c28المعرف الفريد لمؤشر ترابط نظام التشغيل الذي يرتبط به مؤشر ترابط Java.
يتم إخراج هذه القيمة مع التعليمات البرمجية التالية:
السطر 42 في نقطة الاتصال / المشاركة / وقت التشغيل / osThread.cpp :
 st-> print ("nid = 0x٪ x"، thread_id ())؛

الحالةالانتظار بشرطحالة الإنسان المقروء للخيط الحالي.
يعرض هذا السطر معلومات إضافية عن الحالة البسيطة للدفق (انظر أدناه) ، والتي يمكن أن تكون
تستخدم لفهم ما الذي ستفعله سلسلة المحادثات (أي ما إذا كانت سلسلة الرسائل تحاول الحصول على قفل
أو انتظار الوفاء بشرط إلغاء القفل).
آخر مؤشر Java مكدس معروف[0x000000b82a9ff000]آخر مؤشر مكدس معروف مرتبط بهذا الدفق.
يتم الحصول على هذه القيمة باستخدام كود C ++ الأصلي الممزوج برمز Java باستخدام JNI. يتم إرجاع القيمة بواسطة الدالة last_Java_sp () ،
سطر 2886 في نقطة الاتصال / المشاركة / وقت التشغيل / thread.cpp :
   st-> print_cr ("[" INTPTR_FORMAT "]" ، 
     (intptr_t) last_Java_sp () & ~ right_n_bits (12)) ؛

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

حالة الدفق


السطر الثاني هو الحالة الحالية للتيار. يتم سرد حالات الدفق المحتملة في التعداد:
الموضوع.

جديد
RUNNABLE
محظور
انتظار
TIMED_WAITING
أنهى

راجع الوثائق لمزيد من التفاصيل.

تتبع مكدس الصفحات


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

ومع ذلك ، هناك شيء مثير للاهتمام حول تتبع مؤشر الترابط Thread-02 يختلف عن التتبع القياسي:

 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec انتظار إدخال الشاشة [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (على مراقبة الكائن)
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465b0> (كائن java.lang.Object)
     - مقفل <0x00000000894465a0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    المزامنة المؤمنة التي يمكن قفلها:
     - لا شيء

في التتبع ، نرى أنه تمت إضافة معلومات حول القفل. يتوقع مؤشر الترابط هذا تأمينًا للكائن بعنوان 0x00000000894465b0 (نوع الكائن java.lang.Object). علاوة على ذلك ، فإن الخيط نفسه يحمل القفل بعنوان 0x00000000894465a0 (أيضًا java.lang.Object). ستكون هذه المعلومات مفيدة لنا لاحقًا لتشخيص حالة الجمود.

أساسيات المزامنة الملتقطة (المزامنة الخاصة)


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

وفقًا لوثائق Java الرسمية ، يعد Ownable Synchronizer من نسل AbstractOwnableSynchronizer (أو الفئة الفرعية) ، والتي يمكن التقاطها حصريًا بواسطة الدفق لأغراض المزامنة.

ReentrantLock و lock-lock ، ولكن ليس قفل القراءة لفئة ReentrantReadWriteLock هما مثالان جيدان لمثل هذه "المزامنات القابلة للتملك" التي تقدمها المنصة.

لمزيد من المعلومات حول هذا الموضوع ، يمكنك الرجوع إلى هذا
آخر .

خيوط JVM


يحتوي القسم التالي من التفريغ على معلومات حول سلاسل عمليات JVM الفنية التي ليست جزءًا من التطبيق وترتبط بسلاسل عمليات نظام التشغيل. لأن تعمل هذه التدفقات خارج التطبيق ، وليس لديهم معرفات دفق. في معظم الأحيان ، هذه هي خيوط جامع القمامة وسلاسل JVM الفنية الأخرى:

 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 قابل للتشغيل  
 "G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 قابل للتشغيل  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 قابل للتشغيل  
 "G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c قابل للتشغيل  
 "G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 قابل للتشغيل  
 "G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 قابل للتشغيل  
 "G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 قابل للتشغيل  
 os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 قابل للتشغيل  
 "VM Periodic Task Thread" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 قيد الانتظار

روابط JNI العالمية


يشير هذا القسم إلى عدد المراجع العالمية التي يستخدمها JVM من خلال JNI. لا يتم عرض هذه الارتباطات بواسطة جامع البيانات المهملة وقد تتسبب في حدوث تسرب للذاكرة في ظروف معينة.

 المراجع العالمية لـ JNI: 2

في معظم الحالات البسيطة ، لا يتم استخدام هذه المعلومات. ومع ذلك ، يجب فهم أهمية المراجع العالمية. انظر في هذا المنصب لمزيد من التفاصيل.

المواضيع المسدودة


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

 العثور على طريق مسدود على مستوى جافا:
 ===============================
 "مؤشر ترابط 0":
   في انتظار قفل الشاشة 0x00000250e4982480 (الكائن 0x00000000894465b0 ، java.lang.Object) ،
   الذي عقده "Thread-1"
 "الخيط 1":
   في انتظار قفل الشاشة 0x00000250e4982380 (الكائن 0x00000000894465a0 ، a java.lang.Object) ،
   الذي عقده "Thread-0"
 معلومات مكدس Java للخيوط المذكورة أعلاه:
 =================================================== =
 "مؤشر ترابط 0":
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465b0> (كائن java.lang.Object)
     - مقفل <0x00000000894465a0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "الخيط 1":
     في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - في انتظار قفل <0x00000000894465a0> (كائن java.lang.Object)
     - مؤمن <0x00000000894465b0> (a java.lang.Object)
     على java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 تم العثور على حالة توقف تام 1.

يصف القسم الفرعي الأول سيناريو الجمود:

يتوقع مؤشر الترابط -0 أن يكون قادرًا على التقاط الشاشة (هذا هو الوصول إلى كتلة (secondResource) المتزامنة في تطبيقنا) ، وفي الوقت نفسه يحتفظ مؤشر الترابط هذا بشاشة تحاول التقاط مؤشر الترابط 1 (هذا هو الوصول إلى نفس جزء التعليمات البرمجية: متزامن (secondResource ) في طلبنا).

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



في القسم الفرعي الثاني ، يتم إعطاء تتبع المكدس لكل من مؤشرات الترابط المحظورة.

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

في DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34) ، ثم سنرى جزء المشكلة من الرمز:

 printLockedResource (secondResource) ؛

هذا الخط هو السطر الأول من الكتلة المتزامنة ، وهو سبب القفل ، ويخبرنا أن المزامنة في secondResource هي سبب القفل المتبادل. لتصحيح الوضع ، يجب أن نتأكد من أن كل من مؤشرات الترابط لها نفس ترتيب المزامنة على الموارد ResourceA و ResourceB. إذا فعلنا ذلك ، فسنأتي إلى التطبيق التالي:

 public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized (firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized (secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

سينتهي هذا التطبيق دون التداخل ، ونتيجة لذلك سوف نحصل على النتيجة التالية (لاحظ أن عناوين فئة الكائن قد تغيرت):

 مؤشر ترابط 0: المورد المؤمن -> java.lang.Object@1ad895d1
 مؤشر ترابط 0: المورد المؤمن -> java.lang.Object@6e41d7dd
 مؤشر الترابط 1: مورد مقفل -> java.lang.Object@1ad895d1
 مؤشر الترابط 1: مورد مقفل -> java.lang.Object@6e41d7dd

, , thread dump, . ( deadlock-). , .

Thread Dump-


.

JVM . ( , ).

.

- — Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .

TDA:


. .

الخلاصة


Thread dump- — Java-, . , .

deadlock, . . , — .

, Java- thread dump-. , .

, thread dump — « » , , Java-.

Java . , deadlock- .

, .

Source: https://habr.com/ru/post/ar427513/


All Articles