Angenommen, wir haben folgendes Szenario: Ein Java-Service, der gestern noch einwandfrei funktionierte, konsumiert plötzlich 90% der CPU-Leistung und reagiert kaum noch auf Benutzeranfragen. Anwender erleben Timeouts, und das Operations-Team steht unter Druck, den Vorfall umgehend zu analysieren. In solchen Situationen – ob die Anwendung “blockiert” wirkt oder CPU-Sättigung aufgetreten ist – ist eines der leistungsfähigsten Diagnosewerkzeuge der Thread-Dump. Ein Thread-Dump erfasst den Zustand aller JVM-Threads zu einem bestimmten Zeitpunkt und zeigt Ausführungszustände, Stack-Traces und Lock-Contention – das Rohmaterial, das benötigt wird, um zu verstehen, was die Laufzeitumgebung tatsächlich tut.
Die konventionelle Thread-Dump-basierte Fehlersuche ist jedoch nicht ohne Schwierigkeiten. Die Interpretation dieser Snapshots erfordert tiefgreifendes JVM-Wissen – eine Know-How, das typischerweise nur wenige Entwickler in einem Team besitzen. Die manuelle Analyse ist langsam und fehleranfällig, besonders wenn Hunderte von Threads beteiligt sind, und Teams lösen die Thread-Erfassung oft erst aus, nachdem Latenz und Durchsatz bereits beeinträchtigt wurden – was bedeutet, dass Erkenntnisse zu spät kommen und die Auswirkungen auf Kunden bereits eingetreten sind.
Generative KI kann diesen Prozess schneller und zugänglicher machen. In diesem Artikel zeigen wir, wie eine automatisierte Analyse-Pipeline aufgebaut werden kann, die traditionelle Observability-Komponenten wie Prometheus und Grafana mit einer KI-Schicht kombiniert und Amazon Bedrock nutzt. Das System überwacht kontinuierlich Java-Workloads auf Amazon Elastic Container Service (Amazon ECS) und Amazon Elastic Kubernetes Service (Amazon EKS), erfasst Thread-Dumps, wenn ungewöhnliche Thread-Aktivität erkannt wird, und erstellt automatisch strukturierte Diagnoseberichte innerhalb von Sekunden.
Die Herausforderung der Java-Fehlersuche in Containern
Jeder Java-Entwickler kennt die Leistungsfähigkeit und Komplexität der JVM. Sie abstrahiert Speicherverwaltung, Threading und I/O plattformübergreifend, aber diese Abstraktion macht Performance-Probleme schwieriger zu diagnostizieren. In Container-Umgebungen wird die Situation noch komplizierter. Container sind kurzlebig, IP-Adressen ändern sich dynamisch, und horizontale Skalierung erzeugt mehrere JVM-Instanzen, deren Verhalten sich unter Last leicht unterscheiden kann. Bis ein Ops-Engineer sich mit einem Pod oder Task verbindet, um Diagnosedaten zu sammeln, wurde der Container möglicherweise bereits neu gestartet.
Die manuelle Inspektion jedes Dumps skaliert ebenfalls nicht. Einige Unternehmenssysteme erzeugen Hunderte von Threads; andere betreiben viele Anwendungen über mehrere Cluster hinweg. In beiden Fällen ist der menschliche Engpass klar: Teams benötigen eine Lösung, die Thread-Dump-Erfassung und -Interpretation automatisiert und dabei die tiefgreifenden JVM-Einblicke beibehält, auf die Experten angewiesen sind.
Überblick über die automatisierte Analyse-Architektur
Die hier beschriebene Lösung wird als Infrastructure as Code (IaC) mit AWS CloudFormation bereitgestellt. Sie vereint drei Funktionsebenen. Prometheus und Grafana bilden die Überwachungs- und Alarmierungsebene und sammeln sowie visualisieren JVM-Metriken von Spring Boot-Anwendungen. AWS Lambda stellt die Orchestrierungsebene bereit, die Thread-Dumps bei Bedarf erfasst und verarbeitet. Schließlich liefert Amazon Bedrock die KI-Analyse-Engine, die rohe Thread-Dumps in umsetzbare Erkenntnisse umwandelt.
Die Überwachungsinfrastruktur erfasst kontinuierlich JVM-Metriken (in unserem Fall jvm_threads_live_threads) von jeder containerisierten Anwendung. Wenn die Thread-Anzahl einen vordefinierten Schwellenwert überschreitet, löst Grafana einen Alarm über einen Webhook aus. Der Alarm ruft eine Lambda-Funktion auf, die automatisch bestimmt, ob der betroffene Service auf Amazon ECS oder Amazon EKS läuft. Abhängig von der Umgebung führt sie die entsprechende Erfassungsmethode aus. Thread-Dumps und Analyseberichte werden in Amazon Simple Storage Service (Amazon S3) zur späteren Inspektion oder Trendanalyse gespeichert.
Der vollständige Code und die Deploymentanweisungen sind im “Java on AWS”-Immersion Day Workshop und auf GitHub verfügbar.
Bereitstellung der Basisinfrastruktur
Um das Setup zu veranschaulichen, verwenden wir eine einfache Java-Anwendung namens UnicornStore, die mit Spring Boot erstellt wurde und Standard-Actuator-Endpunkte bereitstellt. Die Überwachungsumgebung kann direkt mit Hilfe von AWS CloudShell mit wenigen Befehlen bereitgestellt werden.
Um dieser Anleitung zu folgen, wird folgendes benötigt:
- Ein AWS-Konto mit Berechtigungen zum Erstellen von ECS/EKS-Clustern, Lambda-Funktionen und S3-Buckets
- AWS CLI konfiguriert mit entsprechenden Zugangsdaten oder AWS CloudShell
- Grundlegende Vertrautheit mit Java Spring Boot-Anwendungen
- Verständnis von Container-Orchestrierungskonzepten
Der folgende Codeausschnitt erstellt einen S3-Bucket für Deploymentartefakte und stellt die gesamte Infrastruktur über CloudFormation bereit:
curl https://raw.githubusercontent.com/aws-samples/java-on-aws/main/infrastructure/cfn/unicornstore-stack.yaml > unicornstore-stack.yaml
CFN_S3=cfn-$(uuidgen | tr -d - | tr '[:upper:]' '[:lower:]')
aws s3 mb s3://$CFN_S3
aws cloudformation deploy --stack-name unicornstore-stack \
--template-file ./unicornstore-stack.yaml \
--s3-bucket $CFN_S3 \
--capabilities CAPABILITY_NAMED_IAM
Nachdem die Stackerstellung abgeschlossen ist, liefert die CloudFormation-Ausgabe die URL und das Passwort für die integrierte Entwicklungsumgebung, die im Workshop verwendet wird. An diesem Punkt sind die Basisanwendung, der Überwachungs-Stack und die IAM-Berechtigungen für die weitere Konfiguration bereit.
Konfiguration der Observability in einer Java-Anwendung
Unsere Beispielanwendung verwendet Spring Boot Actuator und Micrometer, um detaillierte Metriken von der JVM bereitzustellen. In der application.config sieht die Aktivierung der JMX- und Prometheus-Integration folgendermaßen aus:
spring.jmx.enabled=true
management.endpoints.web.exposure.include=threaddump,prometheus,health,info
management.endpoint.health.probes.enabled=true
management.endpoint.prometheus.access=unrestricted
management.endpoint.health.group.liveness.include=livenessState
management.endpoint.health.group.readiness.include=readinessState
management.metrics.enable.all=true
management.metrics.tags.application=unicorn-store-jmx
Die Prometheus-Registry sammelt Daten wie Heap-Nutzung, Garbage-Collection-Pausen und Live-Thread-Counts. Innerhalb der Anwendung reichert die MonitoringConfig-Klasse diese Metriken mit kontextuellen Tags an, die den Cluster, Container-Namen und Netzwerkinformationen identifizieren. Diese Tags helfen der Lambda-Funktion später zu entscheiden, wie der entsprechende Dump erfasst werden soll:
registry.config().commonTags(
"cluster", cluster,
"cluster_type", clusterType,
"container_name", containerName,
"task_pod_id", taskOrPodId,
"instance", ipAddress,
"container_ip", ipAddress
);
Diese Konfiguration stellt sicher, dass jede Metrik zu ihrem Quell-Container und Orchestrierungssystem zurückverfolgt werden kann.
Automatische Erfassung von Thread-Dumps
Sobald die Überwachungsinfrastruktur aktiv ist, bewertet Grafana kontinuierlich Metriken gegen definierte Schwellenwerte. Wenn die Anzahl der Live-Threads das Limit überschreitet, löst die Alarmierungsregel einen Webhook zur Lambda-Funktion aus. Die Funktion analysiert die eingehende Payload und extrahiert Metadaten wie Cluster-Typ, Namespace, Container-Name und IP-Adresse. Basierend auf diesen Informationen verwendet sie unterschiedliche Strategien für ECS und EKS.
Für Amazon ECS ruft die Funktion direkt den Spring Boot Actuator-Endpunkt auf:
response = requests.get(f"http://{container_ip}:8080/actuator/threaddump", timeout=10)
response.raise_for_status()
Für Amazon EKS verwendet die Lambda-Funktion die Kubernetes-API, um native JVM-Diagnosebefehle innerhalb des betroffenen Pods auszuführen:
# Execute command to capture JVM thread dump from a running container
exec_command = [
'/bin/sh',
'-c',
('if command -v jcmd >/dev/null 2>&1; then '
'PID=$(jcmd | grep -v jcmd | cut -d" " -f1); '
'jcmd $PID Thread.print; '
'elif command -v jstack >/dev/null 2>&1; then '
'PID=$(ps -ef | grep java | grep -v grep | awk \'{print $2}\'); '
'jstack $PID; '
'else echo "Neither jcmd nor jstack found"; '
'fi')
]
logger.info(f"Executing thread dump command in pod {pod_name}, container {container_name}")
resp = stream(
self.core_v1_api.connect_get_namespaced_pod_exec,
pod_name,
namespace,
container=container_name,
command=exec_command,
stderr=True,
stdin=False,
stdout=True,
tty=False
)
Der erfasste Dump wird dann zusammen mit den relevanten Metadaten nach Amazon S3 hochgeladen. Von dort löst die Funktion die KI-Analysephase aus.
KI-gestützte Interpretation von Thread-Dumps
Traditionell erfordert die Analyse eines großen Thread-Dumps umfangreiche JVM-Expertise. Entwickler müssen zwischen blocked, waiting, runnable und timed-waiting Threads unterscheiden, verstehen, welche Locks umkämpft sind, und mögliche Deadlocks erkennen. Die Integration mit Amazon Bedrock automatisiert einen Großteil dieser Überlegungen. Die Lambda-Funktion sendet den Dump-Inhalt an Bedrock mit einem strukturierten Prompt, der das Modell anweist, einen mehrteiligen Bericht zu erstellen, einschließlich einer Zusammenfassung, identifizierter Probleme und Optimierungsempfehlungen.
**Thread Dump Input**:
{thread_dump}
"""
payload = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 8192,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7
})
# Implement exponential backoff with jitter to handle
# Bedrock throttling during high-volume scenarios
# This is critical when multiple alerts trigger simultaneously
max_attempts = 5
base_delay = 10
# Cap backoff to avoid over-waiting
max_delay = 30
for attempt in range(1, max_attempts + 1):
logger.info(f"Invoking Bedrock with payload ... ")
try:
response = bedrock.invoke_model(
modelId="global.anthropic.claude-sonnet-4-20250514-v1:0",
body=payload
)
body = json.loads(response.get("body").read())
return body.get("content")[0].get("text")
Der erfasste Dump wird dann zusammen mit den relevanten Metadaten nach Amazon S3 hochgeladen. Von dort löst die Funktion die KI-Analysephase aus.
KI-gestützte Interpretation von Thread-Dumps
Traditionell erfordert die Analyse eines großen Thread-Dumps umfangreiche JVM-Expertise. Entwickler müssen zwischen blocked, waiting, runnable und timed-waiting Threads unterscheiden, verstehen, welche Locks umkämpft sind, und mögliche Deadlocks erkennen. Die Integration mit Amazon Bedrock automatisiert einen Großteil dieser Überlegungen. Die Lambda-Funktion sendet den Dump-Inhalt an Bedrock mit einem strukturierten Prompt, der das Modell anweist, einen mehrteiligen Bericht zu erstellen, einschließlich einer Zusammenfassung, identifizierter Probleme und Optimierungsempfehlungen.
**Thread Dump Input**:
{thread_dump}
"""
payload = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 8192,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7
})
# Implement exponential backoff with jitter to handle
# Bedrock throttling during high-volume scenarios
# This is critical when multiple alerts trigger simultaneously
max_attempts = 5
base_delay = 10
# Cap backoff to avoid over-waiting
max_delay = 30
for attempt in range(1, max_attempts + 1):
logger.info(f"Invoking Bedrock with payload ... ")
try:
response = bedrock.invoke_model(
modelId="global.anthropic.claude-sonnet-4-20250514-v1:0",
body=payload
)
body = json.loads(response.get("body").read())
return body.get("content")[0].get("text")
Die von Bedrock erzeugte Antwort enthält detaillierte Kommentare darüber, welche Threads blockiert sind, welche Pools gesättigt sind und wo Synchronisierungs-Overhead auftritt. Anstatt manuell nach “BLOCKED”-Einträgen zu suchen oder auf einen Senior-JVM-Spezialisten zu warten, erhalten Entwickler jetzt innerhalb von Sekunden nach einem Alarm eine strukturierte Anleitung.
Beispiel-Workflow und Analyse
Sobald die Umgebung bereitgestellt ist, ist es einfach, ein Szenario zu reproduzieren, das die Automatisierung auslöst. In der “Explore”-Ansicht von Grafana kann die Metrik jvm_threads_live_threads für die UnicornStore-Anwendung abgefragt werden.

Das Dashboard zeigt die Anzahl der Live-Threads zusammen mit Metadaten wie Cluster-Name, Namespace und Task- oder Pod-ID an.
Um Last zu erzeugen, können 500 Threads in der Anwendung erzeugt werden, indem eine POST-Anfrage an den entsprechende REST-Endpunkt gesendet wird. Für Amazon ECS kann der Anwendungs-Endpunkt über den Load Balancer ermittelt werden:
SVC_URL=http://$(aws elbv2 describe-load-balancers --names unicorn-store-spring --query "LoadBalancers[0].DNSName" --output text)
Für Amazon EKS wird der Kubernetes-Ingress-Hostnamen genutzt:
SVC_URL=http://$(kubectl get ingress unicorn-store-spring -n unicorn-store-spring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
Jetzt können die Threads gestartet und die Anzahl überprüft werden:
curl --location --request POST $SVC_URL'/api/threads/start' --header 'Content-Type: application/json'
sleep 2
curl --location --request GET $SVC_URL'/api/threads/count' --header 'Content-Type: application/json'
Der entsprechende API-Endpunkt wird vom ThreadGeneratorService unterstützt, einer dedizierten Spring-Komponente, deren einziger Zweck es ist, synthetischen CPU-Druck zu erzeugen. Für diese Demo lassen wir den Service eine große Anzahl von Worker-Threads spawnen, die bedeutungslose Berechnungsschleifen ausführen – was effektiv eine schwere CPU-Arbeitslast simuliert. In unserem Szenario erstellen wir absichtlich 500 dieser Threads. Sobald die Last aktiv ist, können wir zu den Dashboards in Grafana wechseln – Dashboards → Unicorn Store Dashboards → JVM Metrics – EKS & ECS – und in Echtzeit beobachten, wie sich die JVM unter Stress verhält.
Wenn der Grafana-Alarm ausgelöst wird, ruft er unsere Thread-Dump-Lambda-Funktion über einen Webhook auf – mit einer 60-Sekunden-Verzögerung, die nach dem Auslösen des Alarms konfiguriert ist.

Etwa eine Minute später können wir unseren Amazon S3-Bucket inspizieren: Er enthält jetzt sowohl die erfassten Thread-Dumps als auch die entsprechenden Analysedateien. Unten sehen wir eine Beispielausgabe vom Amazon Bedrock-Inferenceschritt – sie hebt relevante JVM-Zustandsbeobachtungen hervor und enthält konkrete Verbesserungsvorschläge für die Anwendung.

Bereinigung der Umgebung
Um alle während dieser Anleitung erstellten Ressourcen zu entfernen, führen wir die im Workshop-Repository bereitgestellten Bereinigungsskripte aus. Die folgenden Befehle löschen die EKS-Add-ons, S3-Buckets und CloudFormation-Stacks, die zuvor erstellt wurden:
~/java-on-aws/infrastructure/scripts/cleanup/eks.sh
aws cloudformation delete-stack --stack-name eksctl-unicorn-store-addon-iamserviceaccount
aws s3 rm s3://$S3PROFILING --recursive
aws s3 rb s3://$S3PROFILING
aws cloudformation delete-stack --stack-name unicornstore-stack
Es sollte immer überprüft werden, dass keine Restressourcen verbleiben, insbesondere S3-Buckets, die für Bereitstellungsartefakte oder Dump-Speicherung verwendet werden.
Fazit
Die Automatisierung der Thread-Dump-Analyse bringt erhebliche Effizienz in die Java-Performance-Fehlersuche und reduziert die Diagnosezeit von Stunden auf Minuten. Anstatt sich bei jedem Vorfall auf manuelle Inspektion und tiefgreifendes JVM-Fachwissen zu verlassen, können Entwickler jetzt automatisch diagnostische Einblicke innerhalb von Momenten nach einem Alarm erhalten. Die Integration von Prometheus, Grafana, AWS Lambda und Amazon Bedrock transformiert die Thread-Dump-Interpretation von einem reaktiven, zeitaufwändigen Prozess in einen automatisierten, wiederholbaren Workflow.
Über die Beschleunigung der Ursachenanalyse hinaus schaffen die gespeicherten Thread-Dumps und ihre entsprechenden KI-Berichte in Amazon S3 eine wertvolle historische Aufzeichnung. Im Laufe der Zeit offenbart diese Daten wiederkehrende Muster wie Thread-Leaks oder periodische Contention in bestimmten Code-Pfaden und ermöglicht proaktive Optimierung, bevor Probleme Benutzer beeinträchtigen. Die gleiche Architektur kann leicht auf andere Diagnoseartefakte wie Heap-Dumps oder Anwendungsprotokolle erweitert oder mit Incident-Management-Systemen wie PagerDuty und OpsGenie für automatisierte Ticket-Anreicherung integriert werden.
Für Java-Teams, die in containerisierten Umgebungen arbeiten, schließt dieser Ansatz die Lücke zwischen Observability und dem Verstehen des Problems. Durch die Kombination traditioneller JVM-Metriken mit KI-gesteuerter Interpretation gewinnen Entwickler tiefere Einblicke in das Verhalten ihrer Anwendungen und können auf Performance-Probleme reagieren, lange bevor sie Benutzer beeinträchtigen. Stellen Sie die vollständige Lösung aus dem Java on AWS GitHub-Repository bereit und beginnen Sie heute mit dem Aufbau Ihrer Diagnoseintelligenz.