Performance tuning – pierwsze kroki z JVM

TL;DR: Performance tuning JVM to proces optymalizacji heap size, garbage collection i JIT compiler. Kluczowe narzędzia to jstat, jvisualvm i flight recorder. Najważniejsze flagi: -Xms/-Xmx (heap), -XX:+UseG1GC (GC), -XX:MaxMetaspaceSize (metaspace).

Dlaczego performance tuning JVM jest kluczowy?

Domyślne ustawienia JVM są zaprojektowane dla przeciętnych aplikacji, ale każdy system ma unikalne charakterystyki. Odpowiednie strojenie JVM może poprawić wydajność o 20-80% i znacznie zredukować koszty infrastruktury. W środowiskach produkcyjnych nawet kilkuprocentowa poprawa może przełożyć się na tysiące dolarów oszczędności rocznie.

Performance tuning to nie jednorazowa czynność – wymagania aplikacji zmieniają się wraz z jej rozwojem i skalowaniem.

Co się nauczysz:

  • Jak analizować wydajność JVM za pomocą wbudowanych narzędzi
  • Optymalizacja heap memory i garbage collection
  • Tuning JIT compiler dla lepszej wydajności CPU
  • Monitorowanie key metrics w środowisku produkcyjnym
  • Systematyczne podejście do performance tuning
Wymagania wstępne: Znajomość Java 8, podstawy JVM (heap/stack), doświadczenie z profilowaniem aplikacji, dostęp do środowiska testowego podobnego do produkcji.

Anatomia wydajności JVM – key areas

Performance tuning JVM koncentruje się na trzech głównych obszarach:

Heap Memory: Obszar pamięci gdzie przechowywane są obiekty Java. Źle skonfigurowany heap to najczęstsza przyczyna problemów z wydajnością.

Obszary optymalizacji JVM

ObszarWpływ na wydajnośćKluczowe parametry
Heap MemoryCzęstotliwość GC, throughput-Xms, -Xmx, -XX:NewRatio
Garbage CollectorLatency, pause times-XX:+UseG1GC, -XX:MaxGCPauseMillis
JIT CompilerCPU utilization, warm-up time-XX:CompileThreshold, -XX:TieredCompilation
MetaspaceClass loading, memory leaks-XX:MaxMetaspaceSize

Narzędzia do diagnozy wydajności

jstat – monitoring garbage collection

Najważniejsze narzędzie do monitorowania GC w czasie rzeczywistym:

# Monitoring GC co 5 sekund
jstat -gc [PID] 5s

# Przykładowe wyjście:
# S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
# 512.0  512.0   0.0   32.0   4608.0   1024.0   11264.0    2048.0  21248.0 20534.3 2560.0 2361.6    10    0.150     2    0.100    0.250

# Interpretacja kolumn:
# S0C/S1C - rozmiar survivor spaces
# EU - eden space utilization 
# OU - old generation utilization
# YGC/YGCT - young generation collections/time
# FGC/FGCT - full GC collections/time
Pro tip: Jeśli FGCT (Full GC Time) stanowi więcej niż 5% całkowitego czasu działania aplikacji, masz problem z GC tuning.

jvisualvm – graficzny profiler

Wbudowane narzędzie graficzne dostępne w JDK:

# Uruchomienie VisualVM
jvisualvm

# Najważniejsze funkcje:
# - Monitor: heap/CPU/threads w czasie rzeczywistym
# - Sampler: profiling metod bez instrumentacji
# - Profiler: szczegółowa analiza hot spots
# - MBeans: zarządzanie JMX beans

Java Flight Recorder (JFR)

Narzędzie low-overhead do szczegółowego profilowania:

# Włączenie JFR dla aplikacji
java -XX:+UnlockCommercialFeatures \
     -XX:+FlightRecorder \
     -XX:StartFlightRecording=duration=60s,filename=myapp.jfr \
     MyApplication

# Analiza nagrania
jmc myapp.jfr  # Java Mission Control
Uwaga: Java Flight Recorder wymaga licencji Oracle JDK w środowisku produkcyjnym (stan na 2016). OpenJDK Flight Recorder będzie dostępny dopiero w Java 11.

Heap Memory Tuning

Sizing heap memory

Podstawowe parametry heap memory:

# Podstawowe ustawienia heap
-Xms2g          # Initial heap size
-Xmx4g          # Maximum heap size  
-XX:NewRatio=3  # Old/Young generation ratio (3:1)
-XX:SurvivorRatio=8  # Eden/Survivor ratio (8:1:1)

# Przykład dla aplikacji web (4GB RAM dostępne)
java -Xms1g -Xmx3g -XX:NewRatio=2 \
     -XX:+UseG1GC \
     MyWebApplication

Strategia sizing heap

Zasada 80/20: Heap powinien zajmować maksymalnie 80% dostępnej RAM, pozostawiając miejsce na OS i off-heap memory.
  1. Zaczynaj konserwatywnie: Xms = 25% RAM, Xmx = 50% RAM
  2. Monitoruj heap utilization: optimal = 70-80% po full GC
  3. Dostrajaj stopniowo: zwiększaj heap jeśli utilization > 85%
  4. Young generation: większy młody heap = rzadsze GC ale dłuższe pauzy

Analiza heap dump

# Generowanie heap dump
jcmd [PID] GC.run_finalization
jcmd [PID] VM.gc  # Force GC przed dump
jmap -dump:format=b,file=heapdump.hprof [PID]

# Analiza za pomocą Eclipse MAT lub VisualVM
# Szukaj:
# - Duplicate strings/objects
# - Large arrays 
# - Memory leaks (objects nie GC'd)
# - Dominators (obiekty zajmujące najwięcej pamięci)

Garbage Collection Tuning

Wybór garbage collector

GC AlgorithmBest ForPause TimesThroughput
Serial GCSingle core, small appsHighGood for small heap
Parallel GCMulti-core, batch processingMedium-HighHigh
CMS GCLow latency applicationsLowMedium
G1 GCLarge heap (>4GB), balancedPredictableGood

G1 Garbage Collector tuning

G1GC to najnowocześniejszy collector w Java 8 (będzie domyślny w Java 9):

# Podstawowa konfiguracja G1GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200    # Target pause time
-XX:G1HeapRegionSize=16m    # Region size (1-32MB)
-XX:G1NewSizePercent=20     # Min young gen size
-XX:G1MaxNewSizePercent=40  # Max young gen size
-XX:G1MixedGCCountTarget=8  # Mixed GC cycles

# Przykład konfiguracji dla aplikacji web:
java -Xms2g -Xmx8g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=100 \
     -XX:G1HeapRegionSize=16m \
     MyWebApp
Pro tip: G1GC automatycznie dostosowuje young generation size aby osiągnąć target pause time. Nie ustawiaj -Xmn gdy używasz G1.

CMS Garbage Collector dla low-latency

# CMS GC dla aplikacji wymagających niskiej latency
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=70  # Start CMS at 70% old gen
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+CMSClassUnloadingEnabled          # Clean up perm gen/metaspace

# Monitoring CMS
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps

JIT Compiler Optimization

Tiered Compilation

Java 8 używa tiered compilation domyślnie:

# Tiered compilation levels:
# Level 0: interpreter
# Level 1: C1 compiler (client)
# Level 2: C1 with limited profiling  
# Level 3: C1 with full profiling
# Level 4: C2 compiler (server) - highest optimization

# Monitoring JIT compilation
-XX:+PrintCompilation
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining

# Tuning compilation thresholds
-XX:CompileThreshold=10000      # Default method invocation threshold
-XX:Tier3CompileThreshold=2000  # C1 compilation threshold
-XX:Tier4CompileThreshold=15000 # C2 compilation threshold

Code Cache tuning

# Code cache sizing
-XX:InitialCodeCacheSize=64m
-XX:ReservedCodeCacheSize=256m
-XX:CodeCacheExpansionSize=2m

# Monitoring code cache
jstat -compiler [PID]  # Compilation statistics
jcmd [PID] Compiler.codecache  # Code cache usage
Pułapka: Code cache overflow powoduje wyłączenie JIT compiler i drastyczny spadek wydajności. Monitoruj code cache usage w długo działających aplikacjach.

Monitoring i key metrics

Kluczowe metryki wydajności

MetrykaTarget ValueNarzędzie
GC Overhead< 5% total timejstat, GC logs
Max Pause Time< 100ms for web appsGC logs
Heap Utilization70-80% after full GCjstat, VisualVM
Allocation RateDepends on appFlight Recorder
CPU Usage< 80% sustainedtop, VisualVM
Thread CountStable, no leaksjstack, VisualVM

Continuous monitoring setup

# Production monitoring flags
-XX:+PrintGC
-XX:+PrintGCDetails  
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-Xloggc:/var/log/myapp/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=100M

# JMX monitoring
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Systematyczne podejście do tuning

Performance tuning workflow

  1. Baseline measurement: Zmierz current performance
  2. Identify bottlenecks: CPU, memory, GC, IO?
  3. Hypothesis: Co może być przyczyną problemu?
  4. Single change: Jedna zmiana na raz
  5. Measure impact: A/B testing with metrics
  6. Document results: Co działało, co nie
Pro tip: Nigdy nie zmieniaj wielu parametrów jednocześnie. Performance tuning to proces empiryczny wymagający kontrolowanych eksperymentów.

Common tuning scenarios

High Allocation Rate:
• Increase young generation size
• Optimize object pooling
• Review algorithm efficiency
Frequent Full GC:
• Increase heap size
• Check for memory leaks
• Tune GC algorithm parameters
High CPU with low throughput:
• Check GC overhead
• Profile hot methods
• Consider JIT compiler tuning

Production deployment best practices

JVM flags for production

# Recommended production flags (Java 8, web application)
java -server \
     -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+UnlockExperimentalVMOptions \
     -XX:+UseCGroupMemoryLimitForHeap \
     -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps \
     -Xloggc:gc.log -XX:+UseGCLogFileRotation \
     -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/heapdumps/ \
     -Djava.awt.headless=true \
     MyApplication
Jak często powinienem tuningować JVM w produkcji?

Tuning powinien być częścią początkowego deployment i potem reaktywny na zmiany w charakterystyce aplikacji. Monitoruj metryki ciągle, ale zmiany wprowadzaj tylko gdy obserwujesz degradację wydajności.

Czy powinienem używać różnych ustawień dla dev/test/prod?

Tak, środowiska powinny mieć proporcjonalne ustawienia. Dev może używać mniejszego heap, ale proporcje i GC algorithm powinny być podobne do produkcji dla lepszego testowania.

G1GC czy CMS – który wybrać w 2016?

G1GC dla aplikacji z heap > 4GB i wymaganiami balanced latency/throughput. CMS dla ultra-low latency applications z mniejszym heap. Parallel GC dla batch processing z focus na throughput.

Jak interpretować GC logi?

Szukaj: pause times (powinny być < 100ms dla web apps), frequency (młody GC co kilka sekund jest OK, full GC rzadko), heap utilization po GC (70-80% to optimum). Użyj GCViewer do analizy.

Jakie są symptomy źle skonfigurowanej JVM?

Długie pause times (>1s), frequent full GC (>1/minute), high GC overhead (>10% CPU time), OutOfMemoryError, degrading performance over time, high CPU usage z niskim throughput.

Czy większy heap zawsze oznacza lepszą wydajność?

Nie! Zbyt duży heap może powodować długie pause times podczas full GC i gorsze locality of reference. Optimal heap size to balance między frequency a duration of GC.

Jak tuningować JVM w kontenerach Docker?

Używaj -XX:+UseCGroupMemoryLimitForHeap (Java 8u131+) aby JVM respektowała container limits. Ustaw heap na ~70% dostępnej container memory. Monitoruj container memory usage vs JVM heap usage.

🚀 Zadanie dla Ciebie

Stwórz benchmark aplikacji i przetestuj różne konfiguracje GC:

  1. Baseline: Uruchom aplikację z domyślnymi ustawieniami JVM
  2. G1GC test: Skonfiguruj G1GC z MaxGCPauseMillis=100ms
  3. CMS test: Skonfiguruj CMS z CMSInitiatingOccupancyFraction=70
  4. Heap tuning: Przetestuj różne rozmiary heap (1GB, 2GB, 4GB)

Porównaj metryki: average latency, 95th percentile latency, throughput, GC overhead. Udokumentuj wyniki i wybierz najlepszą konfigurację.

Przydatne zasoby:

Jakie są twoje doświadczenia z tuningiem JVM? Które metryki uważasz za najważniejsze w monitoringu produkcyjnym?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry