Java >> Java tutorial >  >> JVM

Diagnosticering af en kørende JVM

1. Oversigt

Java Virtual Machine (JVM) er en virtuel maskine, der gør det muligt for en computer at køre Java-programmer. I denne artikel vil vi se, hvordan vi nemt kan diagnosticere en kørende JVM.

Vi har mange værktøjer tilgængelige i selve JDK, som kan bruges til forskellige udviklings-, overvågnings- og fejlfindingsaktiviteter. Lad os tage et kig på jcmd , som er ret nem at bruge og kan give en række informationer om en kørende JVM. Desuden jcmd er et anbefalet værktøj fra JDK 7 og fremefter til forbedret JVM-diagnostik med ingen eller minimal ydeevne.

2. Hvad er jcmd ?

Dette er et værktøj, der sender diagnostiske kommandoanmodninger til en kørende JVM. Den skal dog bruges på den samme maskine, som JVM kører på. Yderligere detaljer er tilgængelige i dens dokumentation.

Lad os se, hvordan vi kan bruge dette værktøj med et eksempel på Java-applikation, der kører på en server.

3. Sådan bruges jcmd ?

Lad os oprette en hurtig demo-webapplikation ved hjælp af Spring Initializr med JDK11 . Lad os nu starte serveren og diagnosticere den ved hjælp af jcmd .

3.1. Hentning af PID

Vi ved, at hver proces har et tilknyttet proces-id kendt som PID . Derfor for at få det tilknyttede PID til vores applikation kan vi bruge jcmd som viser alle relevante Java-processer som nedenfor:

[email protected]:/# jcmd
65 jdk.jcmd/sun.tools.jcmd.JCmd
18 /home/pgm/demo-0.0.1-SNAPSHOT.jar
[email protected]:/# 

Her kan vi se PID'et for vores kørende applikation er 18.

3.2. Hent liste over mulige jcmd Brug

Lad os finde ud af mulige muligheder med jcmd PID hjælp kommando til at starte med:

[email protected]:/# jcmd 18 help
18:
The following commands are available:
Compiler.CodeHeap_Analytics
Compiler.codecache
Compiler.codelist
Compiler.directives_add
Compiler.directives_clear
Compiler.directives_print
Compiler.directives_remove
Compiler.queue
GC.class_histogram
GC.class_stats
GC.finalizer_info
GC.heap_dump
GC.heap_info
GC.run
GC.run_finalization
JFR.check
JFR.configure
JFR.dump
JFR.start
JFR.stop
JVMTI.agent_load
JVMTI.data_dump
ManagementAgent.start
ManagementAgent.start_local
ManagementAgent.status
ManagementAgent.stop
Thread.print
VM.class_hierarchy
VM.classloader_stats
VM.classloaders
VM.command_line
VM.dynlibs
VM.flags
VM.info
VM.log
VM.metaspace
VM.native_memory
VM.print_touched_methods
VM.set_flag
VM.stringtable
VM.symboltable
VM.system_properties
VM.systemdictionary
VM.uptime
VM.version
help

De tilgængelige diagnostiske kommandoer kan være forskellige i forskellige versioner af HotSpot VM.

4. jcmd Kommandoer

Lad os udforske nogle af de mest nyttige jcmd kommandomuligheder for at diagnosticere vores kørende JVM.

4.1. VM.version

Dette er for at få JVM grundlæggende detaljer som vist nedenfor:

[email protected]:/# jcmd 18 VM.version
18:
OpenJDK 64-Bit Server VM version 11.0.11+9-Ubuntu-0ubuntu2.20.04
JDK 11.0.11
[email protected]:/# 

Her kan vi se, at vi bruger OpenJDK 11 til vores eksempelapplikation.

4.2. VM.system_properties

Dette vil udskrive alle systemegenskaber, der er indstillet til vores VM. Der kan vises flere hundrede linjer med information:

[email protected]:/# jcmd 18 VM.system_properties
18:
#Thu Jul 22 10:56:13 IST 2021
awt.toolkit=sun.awt.X11.XToolkit
java.specification.version=11
sun.cpu.isalist=
sun.jnu.encoding=ANSI_X3.4-1968
java.class.path=/home/pgm/demo-0.0.1-SNAPSHOT.jar
java.vm.vendor=Ubuntu
sun.arch.data.model=64
catalina.useNaming=false
java.vendor.url=https\://ubuntu.com/
user.timezone=Asia/Kolkata
java.vm.specification.version=11
...

4.3. VM.flags

For vores eksempelapplikation vil dette udskrive alle anvendte VM-argumenter, enten givet af os eller brugt som standard af JVM. Her kan vi bemærke forskellige standard VM-argumenter som nedenfor:

[email protected]:/# jcmd 18 VM.flags            
18:
-XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=260046848 -XX:ConcGCThreads=1 -XX:G1ConcRefinementThreads=4 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=536870912 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=536870912 -XX:MaxMetaspaceSize=268435456 -XX:MaxNewSize=321912832 -XX:MinHeapDeltaBytes=1048576 -XX:NonNMethodCodeHeapSize=5830732 -XX:NonProfiledCodeHeapSize=122913754 -XX:ProfiledCodeHeapSize=122913754 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:ThreadStackSize=256 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC 
[email protected]:/#

Tilsvarende andre kommandoer, såsom VM.command_line , VM.uptime , VM.dynlibs, også give andre grundlæggende og nyttige detaljer om forskellige andre egenskaber, der anvendes.

Alle de ovenstående kommandoer skal hovedsagelig få forskellige JVM -relaterede detaljer. Lad os nu se nærmere på nogle flere kommandoer, der kan hjælpe med fejlfinding relateret til JVM.

4.4. Thread.print

Denne kommando er for at få den øjeblikkelige tråddump. Derfor udskriver den stack-sporet af alle løbende tråde. Følgende er måden at bruge det på, hvilket kan give langt output afhængigt af antallet af tråde i brug:

[email protected]:/# jcmd 18 Thread.print
18:
2021-07-22 10:58:08
Full thread dump OpenJDK 64-Bit Server VM (11.0.11+9-Ubuntu-0ubuntu2.20.04 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x00007f21cc0028d0, length=25, elements={
0x00007f2210244800, 0x00007f2210246800, 0x00007f221024b800, 0x00007f221024d800,
0x00007f221024f800, 0x00007f2210251800, 0x00007f2210253800, 0x00007f22102ae800,
0x00007f22114ef000, 0x00007f21a44ce000, 0x00007f22114e3800, 0x00007f221159d000,
0x00007f22113ce800, 0x00007f2210e78800, 0x00007f2210e7a000, 0x00007f2210f20800,
0x00007f2210f22800, 0x00007f2210f24800, 0x00007f2211065000, 0x00007f2211067000,
0x00007f2211069000, 0x00007f22110d7800, 0x00007f221122f800, 0x00007f2210016000,
0x00007f21cc001000
}

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=2.32ms elapsed=874.34s tid=0x00007f2210244800 nid=0x1a waiting on condition  [0x00007f221452a000]
   java.lang.Thread.State: RUNNABLE
	at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Native Method)
	at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:241)
	at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=0 cpu=0.32ms elapsed=874.34s tid=0x00007f2210246800 nid=0x1b in Object.wait()  [0x00007f22144e9000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait([email protected]/Native Method)
	- waiting on <0x00000000f7330898> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155)
	- waiting to re-lock in wait() <0x00000000f7330898> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:176)
	at java.lang.ref.Finalizer$FinalizerThread.run([email protected]/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.40ms elapsed=874.33s tid=0x00007f221024b800 nid=0x1c runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

Detaljeret diskussion om at fange et tråddump ved hjælp af andre muligheder kan findes her.

4.5. GC.class_histogram

Lad os bruge en anden jcmd  kommando, der vil give vigtige oplysninger om heap-brug. Derudover vil dette liste alle klasser (enten eksterne eller applikationsspecifikke) med mange forekomster. Igen kan listen være på hundredvis af linjer afhængigt af antallet af klasser i brug:

[email protected]:/# jcmd 18 GC.class_histogram
18:
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         41457        2466648  [B ([email protected])
   2:         38656         927744  java.lang.String ([email protected])
   3:          6489         769520  java.lang.Class ([email protected])
   4:         21497         687904  java.util.concurrent.ConcurrentHashMap$Node ([email protected])
   5:          6570         578160  java.lang.reflect.Method ([email protected])
   6:          6384         360688  [Ljava.lang.Object; ([email protected])
   7:          9668         309376  java.util.HashMap$Node ([email protected])
   8:          7101         284040  java.util.LinkedHashMap$Entry ([email protected])
   9:          3033         283008  [Ljava.util.HashMap$Node; ([email protected])
  10:          2919         257000  [I ([email protected])
  11:           212         236096  [Ljava.util.concurrent.ConcurrentHashMap$Node; ([email protected])

Men hvis dette ikke giver et klart billede, kan vi få et bunkedump. Lad os se på det næste.

4.6. GC.heap_dump

Denne kommando vil give et øjeblikkeligt JVM-heap-dump. Derfor kan vi udtrække heap dump til en fil for at analysere senere som nedenfor:

[email protected]:/# jcmd 18 GC.heap_dump ./demo_heap_dump
18:
Heap dump file created
[email protected]:/# 

Her, demo_heap_dump er heap dump filnavnet. Derudover vil dette blive oprettet på samme sted, hvor vores applikationskrukke er placeret.

4.7. JFR Kommandoindstillinger

I vores tidligere artikel diskuterede vi Java-applikationsovervågning ved hjælp af JFR og JMC . Lad os nu se på jcmd kommandoer, som vi kan bruge til at analysere ydeevneproblemer med vores applikation.

JFR (eller Java Flight Recorder) er en profilerings- og hændelsesindsamlingsramme indbygget i JDK. JFR giver os mulighed for at indsamle detaljerede oplysninger på lavt niveau om, hvordan JVM- og Java-applikationer opfører sig. Derudover kan vi bruge JMC at visualisere data indsamlet af JFR . Derfor JFR og JMC sammen skabe en komplet værktøjskæde til løbende at indsamle lav-niveau og detaljerede runtime-oplysninger.

Selvom hvordan man bruger JMC er ikke omfattet af denne artikel, vil vi se, hvordan vi kan oprette en JFR fil ved hjælp af jcmd . JFR er et kommercielt træk. Derfor er den som standard deaktiveret. Det kan dog aktiveres ved hjælp af 'jcmd PID VM.unlock_commercial_features '.

Vi har dog brugt OpenJDK til vores artikel. Derfor JFR er aktiveret for os. Lad os nu generere en JFR fil ved hjælp af jcmd kommando som nedenfor:

[email protected]:/# jcmd 18 JFR.start name=demo_recording settings=profile delay=10s duration=20s filename=./demorecording.jfr
18:
Recording 1 scheduled to start in 10 s. The result will be written to:

/demorecording.jfr
[email protected]:/# jcmd 18 JFR.check
18:
Recording 1: name=demo_recording duration=20s (delayed)
[email protected]:/# jcmd 18 JFR.check
18:
Recording 1: name=demo_recording duration=20s (running)
[email protected]:/# jcmd 18 JFR.check
18:
Recording 1: name=demo_recording duration=20s (stopped)

Vi har oprettet et eksempel på JFR optagelsesfilnavn demorecording.jfr  på samme sted, hvor vores jar-applikation er placeret. Derudover er denne optagelse på 20 sekunder og konfigureret i henhold til kravene.

Derudover kan vi kontrollere status for JFR optagelse ved hjælp af JFR.check  kommando. Og vi kan øjeblikkeligt stoppe og kassere optagelsen ved hjælp af JFR.stop kommando. På den anden side er JFR.dump kommandoen kan bruges til øjeblikkeligt at stoppe og dumpe optagelsen.

4.8. VM.native_memory

Dette er en af ​​de bedste kommandoer, der kan give en masse nyttige detaljer om heap og non-heap  hukommelse på en JVM. Derfor kan dette bruges til at justere hukommelsesforbruget og detektere enhver hukommelseslækage. Som vi ved, kan JVM-hukommelse bredt klassificeres som heap- og non-heap-hukommelse. Og for at få detaljerne om fuldstændig JVM-hukommelsesbrug kan vi bruge dette værktøj. Derudover kan dette være nyttigt til at definere hukommelsesstørrelse for en container-baseret applikation.

For at bruge denne funktion skal vi genstarte vores applikation med yderligere VM-argument, dvs. –XX:NativeMemoryTracking=summary   eller  -XX:NativeMemoryTracking=detail . Bemærk, at aktivering af NMT forårsager en ydelsesomkostning på 5 % -10 %.

Dette vil give os en ny PID til at diagnosticere:

[email protected]:/# jcmd 19 VM.native_memory
19:

Native Memory Tracking:

Total: reserved=1159598KB, committed=657786KB
-                 Java Heap (reserved=524288KB, committed=524288KB)
                            (mmap: reserved=524288KB, committed=524288KB) 
 
-                     Class (reserved=279652KB, committed=29460KB)
                            (classes #6425)
                            (  instance classes #5960, array classes #465)
                            (malloc=1124KB #15883) 
                            (mmap: reserved=278528KB, committed=28336KB) 
                            (  Metadata:   )
                            (    reserved=24576KB, committed=24496KB)
                            (    used=23824KB)
                            (    free=672KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=253952KB, committed=3840KB)
                            (    used=3370KB)
                            (    free=470KB)
                            (    waste=0KB =0.00%)
 
-                    Thread (reserved=18439KB, committed=2699KB)
                            (thread #35)
                            (stack: reserved=18276KB, committed=2536KB)
                            (malloc=123KB #212) 
                            (arena=39KB #68)
 
-                      Code (reserved=248370KB, committed=12490KB)
                            (malloc=682KB #3839) 
                            (mmap: reserved=247688KB, committed=11808KB) 
 
-                        GC (reserved=62483KB, committed=62483KB)
                            (malloc=10187KB #7071) 
                            (mmap: reserved=52296KB, committed=52296KB) 
 
-                  Compiler (reserved=146KB, committed=146KB)
                            (malloc=13KB #307) 
                            (arena=133KB #5)
 
-                  Internal (reserved=460KB, committed=460KB)
                            (malloc=428KB #1421) 
                            (mmap: reserved=32KB, committed=32KB) 
 
-                     Other (reserved=16KB, committed=16KB)
                            (malloc=16KB #3) 
 
-                    Symbol (reserved=6593KB, committed=6593KB)
                            (malloc=6042KB #72520) 
                            (arena=552KB #1)
 
-    Native Memory Tracking (reserved=1646KB, committed=1646KB)
                            (malloc=9KB #113) 
                            (tracking overhead=1637KB)
 
-        Shared class space (reserved=17036KB, committed=17036KB)
                            (mmap: reserved=17036KB, committed=17036KB) 
 
-               Arena Chunk (reserved=185KB, committed=185KB)
                            (malloc=185KB) 
 
-                   Logging (reserved=4KB, committed=4KB)
                            (malloc=4KB #191) 
 
-                 Arguments (reserved=18KB, committed=18KB)
                            (malloc=18KB #489) 
 
-                    Module (reserved=124KB, committed=124KB)
                            (malloc=124KB #1521) 
 
-              Synchronizer (reserved=129KB, committed=129KB)
                            (malloc=129KB #1089) 
 
-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB) 
 

Her kan vi bemærke detaljer om forskellige hukommelsestyper bortset fra Java Heap Memory . Klassen definerer den JVM-hukommelse, der bruges til at gemme klassemetadata. Tilsvarende er tråden definerer den hukommelse, som vores applikationstråde bruger. Og koden giver den hukommelse, der bruges til at lagre JIT- genereret kode, Compileren selv har en vis pladsforbrug og GC fylder også noget.

Derudover er den reserverede kan give et skøn over den nødvendige hukommelse til vores applikation. Og de engagerede viser minimum allokeret hukommelse.

5. Diagnosticer hukommelseslækage

Lad os se, hvordan vi kan identificere, om der er nogen hukommelseslækage i vores JVM. Derfor skal vi først have en baseline. Og derefter nødt til at overvåge i nogen tid for at forstå, om der er nogen konsekvent stigning i hukommelsen i nogen af ​​hukommelsestyperne nævnt ovenfor.

Lad os først baseline JVM-hukommelsesbrug som nedenfor:

[email protected]:/# jcmd 19 VM.native_memory baseline
19:
Baseline succeeded

Brug nu applikationen til normal eller tung brug i nogen tid. Til sidst skal du bare bruge diff  for at identificere ændringen siden baseline  som nedenfor:

[email protected]:/# jcmd 19 VM.native_memory summary.diff
19:

Native Memory Tracking:

Total: reserved=1162150KB +2540KB, committed=660930KB +3068KB

-                 Java Heap (reserved=524288KB, committed=524288KB)
                            (mmap: reserved=524288KB, committed=524288KB)
 
-                     Class (reserved=281737KB +2085KB, committed=31801KB +2341KB)
                            (classes #6821 +395)
                            (  instance classes #6315 +355, array classes #506 +40)
                            (malloc=1161KB +37KB #16648 +750)
                            (mmap: reserved=280576KB +2048KB, committed=30640KB +2304KB)
                            (  Metadata:   )
                            (    reserved=26624KB +2048KB, committed=26544KB +2048KB)
                            (    used=25790KB +1947KB)
                            (    free=754KB +101KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=253952KB, committed=4096KB +256KB)
                            (    used=3615KB +245KB)
                            (    free=481KB +11KB)
                            (    waste=0KB =0.00%)
 
-                    Thread (reserved=18439KB, committed=2779KB +80KB)
                            (thread #35)
                            (stack: reserved=18276KB, committed=2616KB +80KB)
                            (malloc=123KB #212)
                            (arena=39KB #68)
 
-                      Code (reserved=248396KB +21KB, committed=12772KB +213KB)
                            (malloc=708KB +21KB #3979 +110)
                            (mmap: reserved=247688KB, committed=12064KB +192KB)
 
-                        GC (reserved=62501KB +16KB, committed=62501KB +16KB)
                            (malloc=10205KB +16KB #7256 +146)
                            (mmap: reserved=52296KB, committed=52296KB)
 
-                  Compiler (reserved=161KB +15KB, committed=161KB +15KB)
                            (malloc=29KB +15KB #341 +34)
                            (arena=133KB #5)
 
-                  Internal (reserved=495KB +35KB, committed=495KB +35KB)
                            (malloc=463KB +35KB #1429 +8)
                            (mmap: reserved=32KB, committed=32KB)
 
-                     Other (reserved=52KB +36KB, committed=52KB +36KB)
                            (malloc=52KB +36KB #9 +6)
 
-                    Symbol (reserved=6846KB +252KB, committed=6846KB +252KB)
                            (malloc=6294KB +252KB #76359 +3839)
                            (arena=552KB #1)
 
-    Native Memory Tracking (reserved=1727KB +77KB, committed=1727KB +77KB)
                            (malloc=11KB #150 +2)
                            (tracking overhead=1716KB +77KB)
 
-        Shared class space (reserved=17036KB, committed=17036KB)
                            (mmap: reserved=17036KB, committed=17036KB)
 
-               Arena Chunk (reserved=186KB, committed=186KB)
                            (malloc=186KB)
 
-                   Logging (reserved=4KB, committed=4KB)
                            (malloc=4KB #191)
 
-                 Arguments (reserved=18KB, committed=18KB)
                            (malloc=18KB #489)
 
-                    Module (reserved=124KB, committed=124KB)
                            (malloc=124KB #1528 +7)
 
-              Synchronizer (reserved=132KB +3KB, committed=132KB +3KB)
                            (malloc=132KB +3KB #1111 +22)
 
-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)

Over tid, mens GC fungerer, vil vi bemærke en stigning og et fald i hukommelsesforbrug. Men hvis der er en ukontrolleret stigning i hukommelsesforbruget, kan dette være et problem med hukommelseslækage. Derfor kan vi identificere hukommelseslækageområdet, såsom Heap , Tråd, kode, klasse, osv., fra disse statistikker. Og hvis vores applikation har brug for mere hukommelse, kan vi indstille tilsvarende VM-argumenter.

Hvis hukommelseslækagen er i Heap , vi kan tage et heap-dump (som forklaret tidligere) eller måske bare tune Xmx . På samme måde, hvis hukommelseslækagen er i Tråd, vi kan se efter uhåndterede rekursive instruktioner eller indstille Xss .

6. Konklusion

I denne artikel har vi dækket et værktøj til at diagnosticere JVM for forskellige scenarier.

Vi dækkede også jcmd kommando og dens forskellige brug for at få heap dump, thread dump, JFR optagelse til forskellige præstationsrelaterede analyser. Til sidst så vi også på en måde at diagnosticere en hukommelseslækage ved hjælp af jcmd .


Java tag