Τι είναι το Thread Dump και πώς να το αναλύσετε;

Ας μιλήσουμε για την απόρριψη νημάτων και πώς να την αναλύσουμε.

Θα συζητήσουμε επίσης πώς βοηθά ο εντοπισμός των προβλημάτων και ορισμένων από τον αναλυτή που μπορείτε να χρησιμοποιήσετε.

Τι είναι το Thread;

Μια διεργασία είναι ένα πρόγραμμα υπολογιστή που φορτώνεται στη μνήμη του υπολογιστή και βρίσκεται υπό εκτέλεση. Μπορεί να εκτελεστεί από έναν επεξεργαστή ή ένα σύνολο επεξεργαστών. Μια διαδικασία περιγράφεται στη μνήμη με σημαντικές πληροφορίες όπως αποθήκευση μεταβλητών, χειρισμοί αρχείων, μετρητής προγράμματος, καταχωρητές και σήματα κ.λπ.

Μια διαδικασία μπορεί να αποτελείται από πολλές ελαφριές διαδικασίες που ονομάζονται νήματα. Αυτό βοηθά στην επίτευξη παραλληλισμού όπου μια διεργασία χωρίζεται σε πολλαπλά νήματα. Αυτό έχει ως αποτέλεσμα καλύτερη απόδοση. Όλα τα νήματα σε μια διεργασία μοιράζονται τον ίδιο χώρο μνήμης και εξαρτώνται το ένα από το άλλο.

Νήματα Dumps

Όταν εκτελείται η διαδικασία, μπορούμε να ανιχνεύσουμε την τρέχουσα κατάσταση εκτέλεσης των νημάτων στη διεργασία χρησιμοποιώντας νήμα απόθεσης. Ένα νήμα Dump περιέχει ένα στιγμιότυπο όλων των νημάτων που είναι ενεργά σε ένα συγκεκριμένο σημείο κατά την εκτέλεση ενός προγράμματος. Περιέχει όλες τις σχετικές πληροφορίες σχετικά με το νήμα και την τρέχουσα κατάστασή του.

Μια σύγχρονη εφαρμογή σήμερα περιλαμβάνει πολλαπλούς αριθμούς νημάτων. Κάθε νήμα απαιτεί συγκεκριμένους πόρους, εκτελεί ορισμένες δραστηριότητες που σχετίζονται με τη διαδικασία. Αυτό μπορεί να ενισχύσει την απόδοση μιας εφαρμογής καθώς τα νήματα μπορούν να χρησιμοποιήσουν διαθέσιμους πυρήνες CPU.

Αλλά υπάρχουν συμβιβασμούς, π.χ., μερικές φορές πολλά νήματα μπορεί να μην συντονίζονται καλά μεταξύ τους και μπορεί να προκύψει μια κατάσταση αδιεξόδου. Έτσι, αν κάτι πάει στραβά, μπορούμε να χρησιμοποιήσουμε νήμα απόθεσης για να επιθεωρήσουμε την κατάσταση των νημάτων μας.

Απόθεση νημάτων σε Java

Το JVM thread Dump είναι μια λίστα με την κατάσταση όλων των νημάτων που αποτελούν μέρος της διαδικασίας σε αυτό το συγκεκριμένο χρονικό σημείο. Περιέχει πληροφορίες σχετικά με τη στοίβα του νήματος, που παρουσιάζεται ως ίχνος στοίβας. Όπως είναι γραμμένο σε απλό κείμενο, τα περιεχόμενα μπορούν να αποθηκευτούν για έλεγχο αργότερα. Η ανάλυση των απορρίψεων νημάτων μπορεί να βοηθήσει

  • Βελτιστοποίηση της απόδοσης JVM
  • Βελτιστοποίηση της απόδοσης της εφαρμογής
  • Διάγνωση προβλημάτων, π.χ. αδιέξοδο, διαμάχη νήματος κ.λπ.

Δημιουργία Νημάτων Dumps

Υπάρχουν πολλοί τρόποι για να δημιουργήσετε χωματερές νημάτων. Ακολουθούν ορισμένα εργαλεία που βασίζονται στο JVM και μπορούν να εκτελεστούν από τη γραμμή εντολών/τερματικό (CLI εργαλεία) ή τον κατάλογο /bin (εργαλεία GUI) του φακέλου εγκατάστασης της Java.

Ας τα εξερευνήσουμε.

#1. jStack

Ο απλούστερος τρόπος για να δημιουργήσετε μια ένδειξη νήματος είναι χρησιμοποιώντας το jStack. Το jStack αποστέλλεται με το JVM και μπορεί να χρησιμοποιηθεί από τη γραμμή εντολών. Εδώ, χρειαζόμαστε το PID της διαδικασίας για την οποία θέλουμε να δημιουργήσουμε την ένδειξη νήματος. Για να λάβουμε PID μπορούμε να χρησιμοποιήσουμε την εντολή jps όπως φαίνεται παρακάτω.

jps -l

Το jps παραθέτει όλα τα αναγνωριστικά διεργασιών java.

Στα Windows

C:Program FilesJavajdk1.8.0_171bin>jps -l
47172 portal
6120 sun.tools.jps.Jps
C:Program FilesJavajdk1.8.0_171bin>

Σε Linux

[[email protected] ~]# jps -l
1088 /opt/keycloak/jboss-modules.jar
26680 /var/lib/jenkins/workspace/kyc/kyc/target/kyc-1.0.jar
7193 jdk.jcmd/sun.tools.jps.Jps
2058 /usr/share/jenkins/jenkins.war
11933 /var/lib/jenkins/workspace/admin-portal/target/portal-1.0.jar
[[email protected] ~]#

Όπως μπορούμε να δούμε εδώ, λαμβάνουμε μια λίστα με όλες τις διεργασίες java που εκτελούνται. Περιέχει το τοπικό αναγνωριστικό VM για τη διεργασία java που εκτελείται και το όνομα της εφαρμογής στις στήλες ένα και δύο αντίστοιχα. Τώρα, για να δημιουργήσουμε το νήμα απόρριψης, χρησιμοποιούμε το πρόγραμμα jStack με σημαία –l που δημιουργεί μια μεγάλη λίστα εξόδου του dump. Μπορούμε επίσης να διοχετεύσουμε την έξοδο σε κάποιο αρχείο κειμένου της επιλογής μας.

jstack -l 26680

[[email protected] ~]# jstack -l 26680
2020-06-27 06:04:53
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):

"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"logback-8" #2316 daemon prio=5 os_prio=0 tid=0x00007f07e0033000 nid=0x4792 waiting on condition [0x00007f07baff8000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"logback-7" #2315 daemon prio=5 os_prio=0 tid=0x00007f07e0251800 nid=0x4791 waiting on condition [0x00007f07bb0f9000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

#2. jvisualvm

Το Jvisualvm είναι ένα εργαλείο GUI που μας βοηθά να αντιμετωπίζουμε προβλήματα, να παρακολουθούμε και να προφίλ εφαρμογών Java. Έρχεται επίσης με JVM και μπορεί να εκκινηθεί από τον κατάλογο /bin της εγκατάστασης java μας. Είναι πολύ διαισθητικό και εύκολο στη χρήση. Μεταξύ άλλων επιλογών, μας επιτρέπει επίσης να συλλάβουμε το νήμα απόρριψης για μια συγκεκριμένη διαδικασία.

  17 καλύτερα πτυχία μάρκετινγκ στο Διαδίκτυο για να κάνετε αίτηση τώρα

Για να προβάλουμε την ένδειξη νήματος για μια συγκεκριμένη διεργασία, μπορούμε να κάνουμε δεξί κλικ στο πρόγραμμα και να επιλέξουμε την ένδειξη “Απόδειξη νήματος” από το μενού περιβάλλοντος.

#3. jcmd

Το JCMD είναι ένα βοηθητικό πρόγραμμα γραμμής εντολών που αποστέλλεται με το JDK και χρησιμοποιείται για την αποστολή αιτημάτων διαγνωστικών εντολών στο JVM.

Ωστόσο, λειτουργεί μόνο στον τοπικό υπολογιστή όπου εκτελείται η εφαρμογή Java. Μπορεί να χρησιμοποιηθεί για τον έλεγχο Java Flight Recordings, τη διάγνωση και την αντιμετώπιση προβλημάτων εφαρμογών JVM και Java. Μπορούμε να χρησιμοποιήσουμε την εντολή Thread.print του jcmd για να λάβουμε μια λίστα απορριμμάτων νημάτων για μια συγκεκριμένη διαδικασία που καθορίζεται από το PID.

Παρακάτω είναι ένα παράδειγμα για το πώς μπορούμε να χρησιμοποιήσουμε το jcmd.

jcmd 28036 Thread.print

C:Program FilesJavajdk1.8.0_171bin>jcmd 28036 Thread.print
28036:
2020-06-27 21:20:02
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):

"Bundle File Closer" #14 daemon prio=5 os_prio=0 tid=0x0000000021d1c000 nid=0x1d4c in Object.wait() [0x00000000244ef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Unknown Source)
        at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.getNextEvent(EventManager.java:403)
        - locked <0x000000076f380a88> (a org.eclipse.osgi.framework.eventmgr.EventManager$EventThread)
        at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:339)

"Active Thread: Equinox Container: 0b6cc851-96cd-46de-a92b-253c7f7671b9" #12 prio=5 os_prio=0 tid=0x0000000022e61800 nid=0xbff4 waiting on condition [0x00000000243ee000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076f388188> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000021a7b000 nid=0x2184 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x00000000219f5000 nid=0x1300 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x00000000219e0000 nid=0x48f4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x00000000219df000 nid=0xb314 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x00000000219db800 nid=0x2260 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x00000000219d9000 nid=0x125c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000219d8000 nid=0x834 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001faf3000 nid=0x36c0 in Object.wait() [0x0000000021eae000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        - locked <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000005806000 nid=0x13c0 in Object.wait() [0x00000000219af000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference.tryHandlePending(Unknown Source)
        - locked <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)

"main" #1 prio=5 os_prio=0 tid=0x000000000570e800 nid=0xbf8 runnable [0x0000000000fec000]
   java.lang.Thread.State: RUNNABLE
        at java.util.zip.ZipFile.open(Native Method)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at org.eclipse.osgi.framework.util.SecureAction.getZipFile(SecureAction.java:307)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getZipFile(ZipBundleFile.java:136)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.lockOpen(ZipBundleFile.java:83)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getEntry(ZipBundleFile.java:290)
        at org.eclipse.equinox.weaving.hooks.WeavingBundleFile.getEntry(WeavingBundleFile.java:65)
        at org.eclipse.osgi.storage.bundlefile.BundleFileWrapper.getEntry(BundleFileWrapper.java:55)
        at org.eclipse.osgi.storage.BundleInfo$Generation.getRawHeaders(BundleInfo.java:130)
        - locked <0x000000076f85e348> (a java.lang.Object)
        at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:599)
        at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:1)
        at org.eclipse.equinox.weaving.hooks.SupplementerRegistry.addSupplementer(SupplementerRegistry.java:172)
        at org.eclipse.equinox.weaving.hooks.WeavingHook.initialize(WeavingHook.java:138)
        at org.eclipse.equinox.weaving.hooks.WeavingHook.start(WeavingHook.java:208)
        at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startActivator(FrameworkExtensionInstaller.java:261)
        at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startExtensionActivators(FrameworkExtensionInstaller.java:198)
        at org.eclipse.osgi.internal.framework.SystemBundleActivator.start(SystemBundleActivator.java:112)
        at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:815)
        at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:1)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:808)
        at org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:765)
        at org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:1005)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle$EquinoxSystemModule.initWorker(EquinoxBundle.java:190)
        at org.eclipse.osgi.container.SystemModule.init(SystemModule.java:99)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:272)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:257)
        at org.eclipse.osgi.launch.Equinox.init(Equinox.java:171)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.startup(EclipseStarter.java:316)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:251)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:661)
        at org.eclipse.equinox.launcher.Main.basicRun(Main.java:597)
        at org.eclipse.equinox.launcher.Main.run(Main.java:1476)

"VM Thread" os_prio=2 tid=0x000000001fae8800 nid=0x32cc runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000005727800 nid=0x3264 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000005729000 nid=0xbdf4 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000572a800 nid=0xae6c runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000572d000 nid=0x588 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000572f000 nid=0xac0 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000005730800 nid=0x380 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000005733800 nid=0x216c runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000005734800 nid=0xb930 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000021a8d000 nid=0x2dcc waiting on condition

JNI global references: 14


C:Program FilesJavajdk1.8.0_171bin>

#4. JMC

Το JMC σημαίνει Java Mission Control. Είναι ένα εργαλείο ανοιχτού κώδικα GUI που αποστέλλεται με JDK και χρησιμοποιείται για τη συλλογή και ανάλυση δεδομένων εφαρμογών java.

Μπορεί να εκκινηθεί από το φάκελο /bin της εγκατάστασης Java. Οι διαχειριστές και οι προγραμματιστές Java χρησιμοποιούν το εργαλείο για να συγκεντρώσουν λεπτομερείς πληροφορίες χαμηλού επιπέδου σχετικά με τις συμπεριφορές του JVM και της εφαρμογής. Επιτρέπει τη λεπτομερή και αποτελεσματική ανάλυση των δεδομένων που συλλέγονται από το Java Flight Recorder.

Κατά την εκκίνηση του jmc, μπορούμε να δούμε τη λίστα της διαδικασίας java που εκτελείται στον τοπικό υπολογιστή. Είναι επίσης δυνατή μια απομακρυσμένη σύνδεση. Σε μια συγκεκριμένη διαδικασία, μπορούμε να κάνουμε δεξί κλικ και να επιλέξουμε Έναρξη καταγραφής πτήσης και, στη συνέχεια, να ελέγξουμε τα νήματα απόρριψης στην καρτέλα Νήματα.

#5. jconsole

Το jconsole είναι ένα εργαλείο επέκτασης διαχείρισης Java που χρησιμοποιείται για τη διαχείριση και παρακολούθηση παραπόνων.

Διαθέτει επίσης ένα σύνολο προκαθορισμένων λειτουργιών στον πράκτορα JMX που μπορεί να εκτελέσει ο χρήστης. Επιτρέπει στον χρήστη να ανιχνεύει και να αναλύει το stack trace ενός ζωντανού προγράμματος. Μπορεί να εκκινηθεί από το φάκελο /bin της εγκατάστασης Java.

Χρησιμοποιώντας το εργαλείο jconsole GUI, μπορούμε να επιθεωρήσουμε το ίχνος στοίβας κάθε νήματος όταν το συνδέουμε σε μια διεργασία java που εκτελείται. Στη συνέχεια, στην καρτέλα Νήμα, μπορούμε να δούμε το όνομα όλων των νημάτων που εκτελούνται. Για να εντοπίσουμε ένα αδιέξοδο, μπορούμε να κάνουμε κλικ στο Εντοπισμός αδιεξόδου κάτω δεξιά στο παράθυρο. Εάν εντοπιστεί αδιέξοδο, θα εμφανιστεί σε μια νέα καρτέλα, διαφορετικά θα εμφανιστεί η ένδειξη Δεν εντοπίστηκε αδιέξοδο.

  Πώς να προσαρμόσετε το κέλυφος Bash με το shopt

#6. ThreadMxBean

Το ThreadMXBean είναι η διεπαφή για τη διαχείριση του συστήματος νημάτων της εικονικής μηχανής Java που ανήκει στο πακέτο java.lang.Management. Χρησιμοποιείται κυρίως για την ανίχνευση των νημάτων που έχουν εισέλθει σε κατάσταση αδιεξόδου και για τη λήψη λεπτομερειών για αυτά.

Μπορούμε να χρησιμοποιήσουμε τη διεπαφή ThreadMxBean για να καταγράψουμε μέσω προγραμματισμού την ένδειξη νήμα. Η μέθοδος getThreadMXBean() του ManagementFactory χρησιμοποιείται για τη λήψη μιας παρουσίας της διεπαφής ThreadMXBean. Επιστρέφει τον αριθμό των ζωντανών νημάτων τόσο των daemon όσο και των μη-δαίμονων. Το ManagementFactory είναι μια εργοστασιακή κατηγορία για τη λήψη των διαχειριζόμενων φασολιών για την πλατφόρμα Java.

private static String getThreadDump (boolean lockMonitors, boolean lockSynchronizers) {
    StringBuffer threadDump = new StringBuffer (System.lineSeparator ());
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean ();
    for (ThreadInfo threadInfo : threadMXBean.dumpAllThreads (lockMonitors, lockSynchronizers)) {
        threadDump.append (threadInfo.toString ());
    }
    return threadDump.toString ();
}

Manual Analysis of Thread Dumps

Η ανάλυση των απορριμμάτων νημάτων μπορεί να είναι πολύ χρήσιμη για τον εντοπισμό προβλημάτων σε διαδικασίες πολλαπλών νημάτων. Ζητήματα όπως τα αδιέξοδα, η διαμάχη κλειδώματος και η υπερβολική χρήση της CPU από μεμονωμένες αποθήκες νημάτων μπορούν να επιλυθούν οπτικοποιώντας τις καταστάσεις μεμονωμένων απορριμμάτων νημάτων.

Η μέγιστη απόδοση από την εφαρμογή μπορεί να επιτευχθεί διορθώνοντας την κατάσταση κάθε νήματος μετά την ανάλυση της απόρριψης νήματος.

Για παράδειγμα, ας πούμε, μια διεργασία καταναλώνει πολλή CPU, μπορούμε να μάθουμε εάν κάποιο νήμα χρησιμοποιεί τη CPU περισσότερο. Εάν υπάρχει τέτοιο νήμα, μετατρέπουμε τον αριθμό LWP του σε δεκαεξαδικό αριθμό. Στη συνέχεια, από την απόθεση νήματος, μπορούμε να βρούμε το νήμα με nid ίσο με τον προηγουμένως ληφθέν δεκαεξαδικό αριθμό. Χρησιμοποιώντας το stack trace του νήματος μπορούμε να εντοπίσουμε το πρόβλημα. Ας μάθουμε το αναγνωριστικό διαδικασίας του νήματος χρησιμοποιώντας την παρακάτω εντολή.

ps -mo pid,lwp,stime,time,cpu -C java

[[email protected] ~]# ps -mo pid,lwp,stime,time,cpu -C java
       PID        LWP         STIME           TIME              %CPU
26680               -         Dec07          00:02:02           99.5
         -       10039        Dec07          00:00:00           0.1
         -       10040        Dec07          00:00:00           95.5

Ας ρίξουμε μια ματιά στο παρακάτω κομμάτι απόρριψης νήματος. Για να λάβετε ένδειξη νήματος για τη διαδικασία 26680, χρησιμοποιήστε το jstack -l 26680

[[email protected] ~]# jstack -l 26680
2020-06-27 09:01:29
<strong>Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):</strong>

"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

.
.
.
.
.
.
.
"<strong>Reference Handler</strong>" #2 daemon prio=10 os_prio=0 tid=0x00007f085814a000 nid=0x6840 in Object.wait() [0x00007f083b2f1000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000006c790fbd0> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=0 tid=0x00007f0858140800 nid=0x683f runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f0858021000 nid=0x683b runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f0858022800 nid=0x683c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f0858024800 nid=0x683d runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f0858026000 nid=0x683e runnable

"VM Periodic Task Thread" os_prio=0 tid=0x00007f08581a0000 nid=0x6847 waiting on condition

JNI global references: 1553

Τώρα, ας δούμε ποια είναι τα πράγματα που μπορούμε να εξερευνήσουμε χρησιμοποιώντας τα νήματα απόρριψης. Αν παρατηρήσουμε το νήμα απόρριψης, μπορούμε να δούμε πολύ περιεχόμενο, το οποίο μπορεί να είναι συντριπτικό. Ωστόσο, αν κάνουμε ένα βήμα τη φορά, μπορεί να είναι αρκετά απλό να το κατανοήσουμε. Ας καταλάβουμε την πρώτη γραμμή

27-06-2020 09:01:29
Πλήρης ένδειξη νήματος Java HotSpot(TM) 64-bit διακομιστή VM (μικτή λειτουργία 25.221-b11):

Το παραπάνω εμφανίζει την ώρα που δημιουργήθηκε το dump και πληροφορίες σχετικά με το JVM που χρησιμοποιήθηκε. Στη συνέχεια, στο τέλος, μπορούμε να δούμε τη λίστα των νημάτων, το πρώτο από αυτά είναι το νήμα ReferenceHandler.

Ανάλυση αποκλεισμένων νημάτων

Αν αναλύσουμε τα παρακάτω αρχεία καταγραφής νημάτων, μπορούμε να διαπιστώσουμε ότι έχει ανιχνεύσει νήματα με κατάσταση BLOCKED, γεγονός που καθιστά την απόδοση μιας εφαρμογής πολύ αργή. Έτσι, εάν μπορούμε να βρούμε τα BLOCKED threads, μπορούμε να προσπαθήσουμε να εξαγάγουμε τα νήματα που σχετίζονται με τα locks που προσπαθούν να αποκτήσουν τα νήματα. Η ανάλυση του ίχνους στοίβας από το νήμα που συγκρατεί αυτήν τη στιγμή την κλειδαριά μπορεί να βοηθήσει στην επίλυση του προβλήματος.

[[email protected] ~]# jstack -l 26680
.
.
.
.
" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State: <strong>BLOCKED</strong> (on object monitor)
                at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
"DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020]
java.lang.Thread.State: <strong>BLOCKED</strong> (on object monitor)
                at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
.
.
.
.

Αναλύοντας το αδιέξοδο νήμα

Μια άλλη πολύ συχνά χρησιμοποιούμενη εφαρμογή των νημάτων απόρριψης είναι η ανίχνευση αδιεξόδων. Ο εντοπισμός και η επίλυση των αδιεξόδων μπορεί να είναι πολύ πιο εύκολος αν αναλύσουμε τα νήματα απόρριψης.

Ένα αδιέξοδο είναι μια κατάσταση που περιλαμβάνει τουλάχιστον δύο νήματα όπου ο πόρος που απαιτείται από ένα νήμα για να συνεχιστεί η εκτέλεση κλειδώνεται από ένα άλλο νήμα και ταυτόχρονα ο πόρος που απαιτείται από το δεύτερο νήμα κλειδώνεται από το πρώτο νήμα.

  Η ανάκτηση κωδικού πρόσβασης του Excel είναι εύκολη με αυτά τα 8 εργαλεία

Έτσι, κανένα από τα νήματα δεν μπορεί να συνεχίσει την εκτέλεση και αυτό οδηγεί σε κατάσταση αδιεξόδου και καταλήγει στο να κολλήσει η εφαρμογή. Εάν υπάρχουν dreadlocks, τότε το τελευταίο τμήμα του thread dump θα εκτυπώσει τις πληροφορίες σχετικά με το αδιέξοδο ως εξής.

"Thread-0":
waiting to lock monitor 0x00000250e4982480 (object 0x00000000894465b0, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00000250e4982380 (object 0x00000000894465a0, a java.lang.Object),
which is held by "Thread-0"
.
.
.
"Thread-0":
at DeadlockedProgram$DeadlockedRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465b0> (a java.lang.Object)
- locked <0x00000000894465a0> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:844)
"Thread-1":
at DeadlockedProgram $DeadlockRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465a0> (a java.lang.Object)
- locked <0x00000000894465b0> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:844)

Εδώ μπορούμε να δούμε τις πληροφορίες του αδιεξόδου σε μια αρκετά αναγνώσιμη από τον άνθρωπο μορφή.

Εκτός από αυτό, αν συνοψίσουμε όλα τα παραπάνω κομμάτια απόρριψης νημάτων μαζί, τότε δηλώνει τις παρακάτω πληροφορίες.

  • Ο χειριστής αναφοράς είναι το αναγνώσιμο από τον άνθρωπο όνομα του νήματος.
  • Το #2 είναι το μοναδικό αναγνωριστικό του νήματος.
  • δαίμονας υποδηλώνει αν το νήμα είναι νήμα δαίμονα.
  • Η αριθμητική προτεραιότητα του νήματος δίνεται από το prio=10.
  • Η τρέχουσα κατάσταση του νήματος υποδηλώνεται με την αναμονή υπό συνθήκη.
  • Στη συνέχεια βλέπουμε το stack trace, το οποίο περιλαμβάνει τις πληροφορίες κλειδώματος.

Thread Dumps Analyzers

Εκτός από τη χειροκίνητη ανάλυση, υπάρχουν πολλά διαθέσιμα εργαλεία για την ανάλυση των απορριμμάτων νημάτων, τόσο online όσο και εκτός σύνδεσης. Παρακάτω είναι μερικά από τα εργαλεία που αναφέρονται, τα οποία μπορούμε να χρησιμοποιήσουμε με βάση τις απαιτήσεις.

Αρχικά, ας εξερευνήσουμε τα διαδικτυακά εργαλεία.

#1. Γρήγορο νήμα

Γρήγορο νήμα είναι το αγαπημένο εργαλείο ανάλυσης νήματος του μηχανικού DevOps για την αντιμετώπιση πολύπλοκων προβλημάτων παραγωγής. Αυτός είναι ένας διαδικτυακός αναλυτής νημάτων Java, μπορούμε να ανεβάσουμε το νήμα ως αρχείο ή μπορούμε να αντιγράψουμε και να επικολλήσουμε απευθείας το νήμα απόρριψης.

Ανάλογα με το μέγεθος, θα αναλύσει το νήμα απόρριψης και θα εμφανίσει τις πληροφορίες όπως φαίνεται στο στιγμιότυπο οθόνης.

Χαρακτηριστικά

  • Αντιμετώπιση προβλημάτων σφαλμάτων JVM, επιβραδύνσεων, διαρροών μνήμης, παγώματος, αιχμές CPU
  • Instant RCA (μην περιμένετε για προμηθευτές)
  • Διαισθητικό ταμπλό
  • Υποστήριξη REST API
  • Μηχανική Μάθηση

#2. Spotify Thread Dump Analyzer

ο Spotify Thread Dump Analyzer έχει άδεια χρήσης σύμφωνα με την έκδοση 2.0 της άδειας χρήσης Apache. Είναι ένα διαδικτυακό εργαλείο και δέχεται το νήμα απόθεσης ως αρχείο ή μπορούμε να αντιγράψουμε και να επικολλήσουμε απευθείας το νήμα απόρριψης. Ανάλογα με το μέγεθος, θα αναλύσει το νήμα απόρριψης και θα εμφανίσει τις πληροφορίες όπως φαίνεται στο στιγμιότυπο οθόνης.

#3. Κριτική Jstack

Jstack.review αναλύει τις απορρίψεις νημάτων java μέσα από το πρόγραμμα περιήγησης. Αυτή η σελίδα είναι μόνο από την πλευρά του πελάτη.

#4. Ιστότοπος 24×7

Αυτό εργαλείο αποτελεί προϋπόθεση για τον εντοπισμό ελαττωματικών νημάτων που υποβαθμίζουν την απόδοση της εικονικής μηχανής Java (JVM). Ζητήματα όπως τα αδιέξοδα, η διαμάχη κλειδώματος και η υπερβολική χρήση της CPU από μεμονωμένες αποθήκες νημάτων μπορούν να επιλυθούν οπτικοποιώντας τις καταστάσεις μεμονωμένων απορριμμάτων νημάτων.

Η μέγιστη απόδοση από την εφαρμογή μπορεί να επιτευχθεί διορθώνοντας την κατάσταση κάθε νήματος που παρέχεται από το εργαλείο.

Τώρα, ας εξερευνήσουμε εργαλεία εκτός σύνδεσης.

Όταν πρόκειται για δημιουργία προφίλ, μόνο το καλύτερο εργαλείο είναι αρκετά καλό.

#1. JProfiler

JProfiler είναι ένας από τους πιο δημοφιλείς αναλυτές απόρριψης νημάτων μεταξύ των προγραμματιστών Java. Η διαισθητική διεπαφή χρήστη του JProfiler σάς βοηθά να επιλύσετε τα σημεία συμφόρησης απόδοσης, να εντοπίσετε διαρροές μνήμης και να κατανοήσετε ζητήματα νημάτων.

Το JProfiler υποστηρίζει τη δημιουργία προφίλ στις ακόλουθες πλατφόρμες:

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • Σολάρις
  • AIX
  • HP-UX

Ακολουθούν ορισμένες δυνατότητες που κάνουν το JProfiler την κορυφαία επιλογή για τη δημιουργία προφίλ των εφαρμογών μας στο JVM.

Χαρακτηριστικά

  • Υποστηρίζει δημιουργία προφίλ βάσης δεδομένων για JDBC, JPA και NoSQL
  • Διατίθεται επίσης υποστήριξη για την εταιρική έκδοση Java
  • Παρουσιάζει πληροφορίες υψηλού επιπέδου για κλήσεις RMI
  • Αστρική ανάλυση των διαρροών μνήμης
  • Εκτεταμένες δυνατότητες QA
  • Το ενσωματωμένο νήμα προφίλ είναι στενά ενσωματωμένο με τις προβολές προφίλ CPU.
  • Υποστήριξη για πλατφόρμες, IDE και διακομιστές εφαρμογών.

#2. IBM TMDA

IBM Thread and Monitor Dump Analyzer για Java (TMDA) είναι ένα εργαλείο που επιτρέπει τον εντοπισμό κρουσμάτων, αδιεξόδων, διαφωνιών πόρων και σημείων συμφόρησης σε απόθεμα νημάτων Java. Είναι προϊόν της IBM, αλλά το εργαλείο TMDA παρέχεται χωρίς καμία εγγύηση ή υποστήριξη. Ωστόσο, προσπαθούν να διορθώσουν και να βελτιώσουν το εργαλείο με την πάροδο του χρόνου.

#3. ManageEngine

ManageEngine Ο διαχειριστής εφαρμογών μπορεί να βοηθήσει στην παρακολούθηση της μνήμης JVM Heap και Non-Heap. Μπορούμε ακόμη να διαμορφώσουμε τα κατώφλια και να ειδοποιούμαστε μέσω email, SMS κ.λπ., και να διασφαλίσουμε ότι μια εφαρμογή Java είναι καλά συντονισμένη.

#4. YourKit

YourKit αποτελείται από τα παρακάτω προϊόντα που ονομάζονται ως κιτ.

  • Java Profiler – Πλήρως εξοπλισμένο προφίλ χαμηλού γενικού κόστους για πλατφόρμες Java EE και Java SE.
  • YouMonitor – Παρακολούθηση απόδοσης και διαμόρφωση προφίλ των Jenkins, TeamCity, Gradle, Maven, Ant, JUnit και TestNG.
  • .NET Profiler – Εύκολο στη χρήση προφίλ απόδοσης και μνήμης για .NET Framework.

συμπέρασμα

Τώρα ξέρετε πώς είναι χρήσιμα τα νήματα απόρριψης για την κατανόηση και τη διάγνωση προβλημάτων σε εφαρμογές πολλαπλών νημάτων. Με την κατάλληλη η γνώσησχετικά με τις χωματερές νημάτων – τη δομή τους, τις πληροφορίες που περιέχονται σε αυτές κ.λπ. – μπορούμε να τις χρησιμοποιήσουμε για να εντοπίσουμε γρήγορα τις αιτίες των προβλημάτων.