Porozmawiajmy o tworzeniu zrzutów wątków i metodach ich analizy.
Wyjaśnimy również, w jaki sposób ta analiza ułatwia lokalizowanie problemów, a także przedstawimy kilka narzędzi analitycznych, które możesz zastosować.
Definicja wątku
Proces to program komputerowy, który jest załadowany do pamięci operacyjnej komputera i aktualnie wykonywany. Może być przetwarzany przez procesor lub zespół procesorów. W pamięci proces jest opisany za pomocą kluczowych danych, takich jak obszary przechowywania zmiennych, deskryptory plików, licznik instrukcji, rejestry i sygnały.
Pojedynczy proces może obejmować wiele współbieżnych ścieżek wykonania, zwanych wątkami. To podejście ułatwia osiągnięcie równoległości, w której proces jest dzielony na liczne wątki, co przekłada się na wyższą efektywność. Wszystkie wątki danego procesu współdzielą ten sam obszar pamięci i są od siebie zależne.
Czym są zrzuty wątków?
Podczas działania procesu możemy monitorować aktualny stan wykonywania jego wątków za pomocą zrzutów wątków. Zrzut wątku to migawka wszystkich wątków aktywnych w określonym momencie działania programu. Zawiera kluczowe informacje o wątku i jego bieżącym stanie.
W dzisiejszych aplikacjach często wykorzystuje się wiele wątków. Każdy z nich wymaga zasobów i wykonuje określone zadania w ramach procesu. Może to zwiększyć wydajność aplikacji, ponieważ wątki mają możliwość wykorzystywania dostępnych rdzeni procesora.
Istnieją jednak pewne wady, na przykład sytuacje, w których wiele wątków może ze sobą nie współpracować, prowadząc do blokad. W przypadku wystąpienia problemów możemy użyć zrzutów wątków do zbadania kondycji naszych wątków.
Zrzut wątku w Javie
Zrzut wątku w środowisku JVM to spis stanu wszystkich wątków, które w danym momencie są częścią procesu. Zawiera dane dotyczące stosu danego wątku, przedstawione jako ślad stosu. Ponieważ jest to zwykły tekst, jego zawartość można zapisać do późniejszej analizy. Analiza zrzutów wątków może być przydatna w:
- Optymalizacji wydajności JVM
- Poprawie wydajności aplikacji
- Diagnozowaniu problemów, takich jak zakleszczenia czy konflikty między wątkami
Generowanie zrzutów wątków
Zrzuty wątków można wygenerować na wiele sposobów. Poniżej przedstawiono kilka narzędzi opartych na JVM, które można uruchomić z wiersza poleceń lub terminala (narzędzia CLI) lub z katalogu /bin (narzędzia GUI) w katalogu instalacyjnym Java.
Przyjrzyjmy się im.
#1. jStack
Najprostszym sposobem wygenerowania zrzutu wątku jest użycie narzędzia jStack. Jest ono dostarczane wraz z JVM i można je uruchomić z wiersza poleceń. W tym przypadku potrzebujemy identyfikatora PID procesu, dla którego chcemy utworzyć zrzut. Aby uzyskać PID, możemy użyć polecenia jps, jak pokazano poniżej.
jps -l
jps wyświetla listę wszystkich identyfikatorów procesów Java.
W systemie Windows
C:Program FilesJavajdk1.8.0_171bin>jps -l 47172 portal 6120 sun.tools.jps.Jps C:Program FilesJavajdk1.8.0_171bin>
W systemie 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] ~]#
Jak widać, otrzymujemy wykaz wszystkich działających procesów Java. Zawiera identyfikator lokalnej maszyny wirtualnej oraz nazwę aplikacji w pierwszej i drugiej kolumnie. Aby wygenerować zrzut wątku, używamy polecenia jstack z opcją -l, która tworzy rozbudowany zrzut. Wynik można również przekierować do pliku tekstowego.
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 to narzędzie z graficznym interfejsem użytkownika, które pomaga w diagnozowaniu, monitorowaniu i profilowaniu aplikacji Java. Jest ono również dostarczane z JVM i można je uruchomić z katalogu /bin naszej instalacji Java. Jest intuicyjne i łatwe w obsłudze. Oprócz innych funkcji, umożliwia także tworzenie zrzutów wątków dla konkretnych procesów.
Aby wyświetlić zrzut wątku dla danego procesu, możemy kliknąć program prawym przyciskiem myszy i wybrać „Zrzut wątku” z menu kontekstowego.
#3. jcmd
JCMD to narzędzie wiersza poleceń, które jest częścią pakietu JDK i służy do wysyłania żądań diagnostycznych do maszyny JVM.
Działa jednak wyłącznie na komputerze, na którym działa aplikacja Java. Może być używane do zarządzania nagraniami Java Flight Recordings, diagnozowania i rozwiązywania problemów z aplikacjami JVM i Java. Możemy użyć polecenia Thread.print jcmd, aby uzyskać listę zrzutów wątków dla konkretnego procesu, identyfikowanego przez PID.
Poniżej znajduje się przykład użycia 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, czyli Java Mission Control, to narzędzie GUI typu open source, które jest częścią JDK i służy do zbierania i analizowania danych aplikacji Java.
Można je uruchomić z folderu /bin w miejscu instalacji Javy. Administratorzy i programiści Java używają tego narzędzia do zbierania szczegółowych informacji o niskopoziomowych aspektach działania maszyny JVM i aplikacji. Pozwala na dogłębną i wydajną analizę danych zebranych przez Java Flight Recorder.
Po uruchomieniu jmc widzimy listę procesów Java działających na lokalnym komputerze. Istnieje również możliwość nawiązania połączenia zdalnego. Dla wybranego procesu możemy kliknąć prawym przyciskiem myszy i wybrać opcję „Rozpocznij nagrywanie lotu”, a następnie sprawdzić zrzuty wątków na karcie „Wątki”.
#5. jconsole
jconsole to narzędzie Java Management Extension służące do zarządzania i monitorowania aplikacji.
Zawiera też zestaw predefiniowanych operacji na agencie JMX, które może wykonać użytkownik. Umożliwia wykrywanie i analizowanie na żywo śladu stosu programu. Można je uruchomić z katalogu /bin instalacji Java.
Za pomocą graficznego interfejsu jconsole możemy sprawdzić ślad stosu każdego wątku, gdy połączymy się z działającym procesem Java. W zakładce „Wątki” widzimy listę wszystkich działających wątków. Aby wykryć zakleszczenie, należy kliknąć opcję „Wykryj zakleszczenie” w prawym dolnym rogu okna. Jeśli zakleszczenie zostanie wykryte, pojawi się ono na nowej karcie. W przeciwnym razie zostanie wyświetlony komunikat o braku zakleszczenia.
#6. ThreadMxBean
ThreadMXBean to interfejs zarządzania systemem wątków wirtualnej maszyny Java, należący do pakietu java.lang.management. Jest używany głównie do wykrywania wątków, które utknęły w zakleszczeniu i do uzyskiwania informacji o nich.
Zrzut wątku możemy przechwycić programowo za pomocą interfejsu ThreadMxBean. Metoda getThreadMXBean() z ManagementFactory służy do pobrania instancji interfejsu ThreadMXBean. Zwraca liczbę działających wątków, zarówno demonów, jak i zwykłych wątków. ManagementFactory to klasa fabryczna służąca do pozyskiwania zarządzanych komponentów bean dla platformy 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 (); }
Ręczna analiza zrzutów wątków
Analiza zrzutów wątków może być bardzo pomocna w rozwiązywaniu problemów z procesami wielowątkowymi. Problemy takie jak zakleszczenia, rywalizacja o blokadę i nadmierne obciążenie procesora można diagnozować, analizując stany wątków ze zrzutów.
Maksymalną wydajność aplikacji można osiągnąć poprzez optymalizację stanu każdego wątku po analizie zrzutu.
Przykładowo, jeśli proces generuje duże obciążenie procesora, możemy ustalić, który wątek go najbardziej wykorzystuje. Jeśli taki wątek istnieje, zamieniamy jego numer LWP na liczbę szesnastkową. W zrzucie wątków szukamy wątku z identyfikatorem nid równym tej liczbie. Analizując ślad stosu wątku, możemy zlokalizować problem. Identyfikator procesu wątku uzyskamy za pomocą poniższego polecenia.
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
Przyjrzyjmy się teraz fragmentowi zrzutu wątku. Aby uzyskać zrzut dla procesu o ID 26680, użyjemy komendy 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
Sprawdźmy, jakie informacje możemy uzyskać z zrzutów wątków. Analiza zrzutu może być początkowo przytłaczająca, ale podejście krok po kroku może znacznie ułatwić jego zrozumienie. Zaczynamy od pierwszej linii:
2020-06-27 09:01:29
Pełny zrzut wątku Java HotSpot(TM) 64-bitowy serwer VM (tryb mieszany 25.221-b11):
Powyższa linia pokazuje czas wygenerowania zrzutu i informacje o używanej maszynie JVM. Następnie, na końcu, widzimy listę wątków, gdzie pierwszym jest ReferenceHandler.
Analiza zablokowanych wątków
Analizując poniższe dzienniki zrzutów wątków, możemy stwierdzić, że wykryto wątki o stanie BLOCKED, co powoduje spowolnienie aplikacji. Po zidentyfikowaniu takich wątków możemy spróbować wyodrębnić informacje o blokadach, o które wątki próbują się ubie
newsblog.pl