Dimensionierung und JVM-Konfiguration
Die meisten Einstellungen des jadice web toolkit sind sinnvoll voreingestellt. Mindestens die Dimensionierung der Server und die Konfiguration der Caches müssen jedoch zur erwarteten Last passen. Die folgenden Werte sind Erfahrungswerte und dienen als Startpunkt. Verbindlich werden sie erst durch einen Performance-Test mit dem eigenen Dokumentenbestand. Gerne unterstützt Sie dabei der jadice support.
Hintergründe zum Ressourcenverbrauch beschreibt das Kapitel Dimensionierung der Hardware und Caching.
Server-Instanzen
Erfahrungswerte für 500–1000 gleichzeitige Benutzer:
| Ressource | Richtwert |
|---|---|
| Instanzen (Pods/VM) | 2–4; mit 2 Instanzen starten und bei Bedarf hochskalieren |
| CPU | 4 logische Kerne pro Instanz, im Container als CPU-Limit gesetzt |
| RAM | (6–)8 GB pro Instanz |
| Heap | (4–)6 GB, z. B. über -XX:MaxRAMPercentage=75.0 (siehe JVM-Parameter) |
Ab zwei Instanzen ist ein Load-Balancer mit Session Affinity erforderlich.
Ohne CPU-Limit ermittelt die JVM im Container unter Umständen nur einen Prozessor, und die Thread-Pools starten entsprechend klein (siehe Betrieb in einer Container-Umgebung).
Die passende Dimensionierung hängt stark vom Dokumentenbestand ab:
- PDF kostet gegenüber anderen Formaten mehr Rechenzeit. Planen Sie weniger Benutzer bzw. mehr Prozessoren pro Instanz ein.
- Große Dokumente (viele Seiten, große Formate wie DIN A0, hohe Auflösung) benötigen mehr Heap. Die Rasterung erfolgt in Java; eine A4-Seite bei 300 dpi in ARGB belegt rund 35 MB Heap, bei 600 dpi rund 139 MB – pro gleichzeitig gehaltener Seite.
Tile-Format im Container
Verwenden Sie im Container-Betrieb ein PNG-basiertes Tile-Format (Default
PNGJ_BEST_SPEED oder IMAGEIO) und kein WebP. Die Erzeugung von WebP-Bildern benötigt
Off-Heap-Speicher, der im JVM-Monitoring kaum sichtbar ist und sich schlecht begrenzen lässt.
Mit PNG kann die Lücke zwischen Container- und Heap-Limit kleiner ausfallen, ohne dass der
Container mit OOMKilled beendet wird.
webtoolkit:
tileCompressionType: IMAGEIO
Alle Werte des Tile-Formats beschreibt Application Properties.
Verbindungstyp
Nutzen Sie nach Möglichkeit WebSocket. Server Sent Events und Longpoll sind Ausweichlösungen, wenn WebSocket in der Infrastruktur (Proxy, Firewall) nicht durchgängig möglich ist. Siehe Connection API.
Packaging
Bauen Sie eine Spring-Boot-Anwendung als JAR, nicht als WAR. Das WAR-Packaging ist nur für das Traditional Deployment auf einem Application Server gedacht. Hintergrund: Beim Lesen von PDF-Dokumenten werden Klassen dynamisch über den Classpath gesucht, und dieser Classpath-Scan ist im WAR-Packaging von Spring Boot deutlich teurer.
Thread-Pools und Preloading
- Thread-Pools sind anhand der Prozessorzahl sinnvoll voreingestellt. Anpassungen sind selten nötig und wirken sich auf das Gesamtsystem aus. Die Properties beschreibt Application Properties.
- Preloading beschleunigt die Anzeige benachbarter Seiten. Empfohlen ist ein Bereich von 0–3 Seiten, siehe Tile-Rendering.
Serverseitiger Cache
Der Browser-Cache ist immer aktiv. Serverseitig werden Dokument-Strukturinformationen und
Zwischenergebnisse gecacht; das Caching gerenderter Tiles ist standardmäßig ausgeschaltet
(webtoolkit.tileCachingEnabled: false) und sollte es in der Regel auch bleiben.
Richtwerte
Property (jadice.viewer.cache.…) | Empfehlung | Hinweis |
|---|---|---|
maxNumberOfCacheEntries | 90000 | Siehe Berechnung unten. Eher höher wählen, damit der Speicher und nicht die Anzahl der Einträge begrenzt. |
sizeHighwaterMarkPercent | 25 | Anteil am maximalen Heap; bei 8 GB Heap entspricht das 2 GB. |
sizeHighwaterMark | nicht setzen | Stattdessen sizeHighwaterMarkPercent verwenden. |
minimumExpiryAge | 60000 | Mindestverweildauer eines Eintrags in Millisekunden. |
maximumExpiryObjectCountRatio | 10 | Angabe in Prozent. |
maxAgeExpiryEnabled | true | Entfernt Einträge, deren letzter Zugriff länger als maxAge zurückliegt. |
maxAge / maxAge.timeUnit | 60 / MINUTES |
Wie diese Werte gesetzt werden, beschreibt Dimensionierung der Hardware und Caching.
Berechnung der Cache-Größe
Die Richtwerte beruhen auf folgender Abschätzung, die mit der Enterprise Demo und verschiedenen Dokumenten empirisch ermittelt wurde:
- Je Dokument belegt der Cache etwa 2,5–4,5 MB (Dokument, Seitensegmente, Strukturinformationen) und 150–300 Einträge.
- Annahme: Im Mittel werden 10 Dokumente pro Minute geladen, und ein Dokument soll etwa 30 Minuten im Cache bleiben.
- Speicher: 10 × 30 × 2,5 MB = 750 MB bis 10 × 30 × 4,5 MB = 1350 MB
- Einträge: 10 × 30 × 300 = 90 000
Setzen Sie für Ihre Installation die eigene Ladefrequenz und gewünschte Verweildauer ein.
Cache als Engpass
Werden Dokumente zu häufig aus dem Cache verdrängt und neu geladen, obwohl der Cache nur gering
belegt ist, kann der Wechsel auf einen LRUCache helfen. Den Cache setzen Sie im
Einstiegspunkt der Integration:
LRUCache cache = new LRUCache(120000);
cache.setSizeHighwaterMark(1000 * 1024 * 1024);
CacheManager.setDefault(cache);
JVM-Parameter
Empfohlener Parametersatz für Spring Boot im Container mit 8 GB RAM und JDK 25:
-XX:+UseG1GC
-XX:InitialRAMPercentage=75.0
-XX:MaxRAMPercentage=75.0
-XX:MaxMetaspaceSize=384m
-XX:G1HeapRegionSize=16m
-XX:+UseCompactObjectHeaders
-XX:+UseStringDeduplication
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/dumps/heapdump.hprof
-Xlog:gc*:file=/var/log/jvm/gc.log:time,uptime,level,tags:filecount=5,filesize=10M
-XX:+UseCompactObjectHeaders ist erst ab JDK 25 ein reguläres Feature. Unter älteren JDKs
entfällt der Parameter; die JVM startet sonst nicht.
Heap
InitialRAMPercentage und MaxRAMPercentage auf denselben Wert zu setzen fixiert den Heap und
verhindert Resizing im Betrieb. 75 % von 8 GB ergeben etwa 6 GB Heap.
Mehr als 75 % sind ohne Messung nicht zu empfehlen. Außerhalb des Heaps liegen Metaspace, Code
Cache, Thread-Stacks, GC-Metadaten und NIO-Puffer – erfahrungsgemäß zusammen 1–1,5 GB. Reicht
dieser Rest nicht, beendet die Container-Laufzeit den Prozess mit OOMKilled, statt dass die JVM
einen OutOfMemoryError wirft. Dann entsteht auch kein Heap-Dump.
MaxRAMPercentage bezieht sich auf das Speicherlimit des Containers. Ohne
resources.limits.memory im Kubernetes-Manifest liest die JVM den RAM der gesamten Node: Auf
einer 32-GB-Node werden aus 6 GB Heap dann 24 GB.
| Bereich | Größe (8 GB Container) |
|---|---|
| Heap | ca. 6 GB (75 %) |
| Metaspace (Obergrenze) | 384 MB |
| Code Cache, Thread-Stacks, GC-Metadaten, NIO-Puffer | ca. 1–1,5 GB |
| Reserve | Rest |
Die Werte sind Richtwerte. Belastbar wird die Aufteilung durch eine Messung mit Native Memory
Tracking am eigenen Dokumentenbestand: -XX:NativeMemoryTracking=summary temporär setzen
(ca. 5 % Overhead) und mit jcmd <pid> VM.native_memory auswerten.
Region Size
-XX:G1HeapRegionSize=16m ist für das jadice web toolkit der wichtigste GC-Parameter. G1
behandelt Objekte ab der halben Regionsgröße als Humongous und legt sie direkt in der Old
Generation ab. Gerasterte Seiten liegen als große Arrays im Heap und überschreiten den
Default-Schwellwert regelmäßig. Mit 16 MB Regionen liegt die Schwelle bei 8 MB.
Off-Heap-Speicher
Der Parametersatz enthält bewusst kein -XX:MaxDirectMemorySize. Mit PNG als Tile-Format wird
off-heap nur wenig Speicher benötigt. Wird dennoch WebP verwendet, setzen Sie
-XX:MaxDirectMemorySize explizit und senken den Heap-Anteil entsprechend, damit das
Container-Limit nicht unbemerkt überschritten wird.
Heap-Dumps und GC-Log
HeapDumpOnOutOfMemoryError und ExitOnOutOfMemoryError ergänzen sich: Der Dump wird
geschrieben, danach beendet sich die JVM und die Orchestrierung startet den Container neu.
In Kubernetes ist /tmp je nach Manifest ein emptyDir mit medium: Memory (tmpfs). Ein
Heap-Dump landet dann im RAM und zählt gegen dasselbe Speicherlimit. Verwenden Sie ein Volume mit
echtem Storage und mindestens Heap-Größe freiem Platz. Ohne ein solches Volume schalten Sie
HeapDumpOnOutOfMemoryError ab.
Heap-Dumps enthalten Dokumentinhalte und gegebenenfalls personenbezogene Daten. Schützen Sie das Volume entsprechend und löschen Sie Dumps nach der Analyse.
Nicht mehr benötigte Parameter
Folgende Parameter finden sich häufig in älteren Konfigurationen. Unter JDK 25 entsprechen sie dem Default oder sind wirkungslos und können entfallen:
| Parameter | Grund |
|---|---|
-XX:MaxGCPauseMillis=200 | Default in G1. |
-XX:G1ReservePercent=10 | Default. |
-XX:InitiatingHeapOccupancyPercent=45 | Nur Startwert; G1 passt den Wert im Betrieb adaptiv an. |
-XX:MinRAMPercentage=75.0 | Wirkt nur bei Containern unter ca. 250 MB RAM. |
-XX:MetaspaceSize=256m | Setzt nur die Schwelle für die erste Metaspace-GC. |
-XX:+UseContainerSupport | Default seit JDK 10. |
-XX:+UseCompressedOops, -XX:+UseCompressedClassPointers | Default bei Heaps unter 32 GB. |
-XX:+OptimizeStringConcat | Obsolet seit JDK 9. |
-Djava.security.egd=file:/dev/./urandom | Obsolet seit JDK 9. |
-XX:+UnlockExperimentalVMOptions | Für UseCompactObjectHeaders ab JDK 25 nicht mehr nötig. |
-XX:+AlwaysPreTouch | Verlängert den Start deutlich; nur bei gemessenen Latenzspitzen in der Warmlaufphase sinnvoll. |
Parameter gegen das Basis-Image prüfen
Ein unbekanntes Flag in JAVA_TOOL_OPTIONS verhindert den Start der JVM; der Pod landet in
CrashLoopBackOff. Prüfen Sie den Parametersatz deshalb in der CI gegen das tatsächlich
verwendete Basis-Image:
docker run --rm <basisimage> -XX:+UseCompactObjectHeaders -version
Pinnen Sie das Basis-Image im Dockerfile per Digest statt per Tag. Das macht Builds und die SBOM reproduzierbar, was auch für den Nachweis der Komponentenherkunft nach dem Cyber Resilience Act relevant ist.
Weitere Einstellungen
- Tolerantes Lesen von PDF: Je nach Dokumentenbestand kann der Lesemodus
LENIENT_ON_ERRORsinnvoll sein, damit PDFs mit kleineren Syntaxfehlern trotzdem angezeigt werden. Gesetzt wird er imDocumentDataProviderdirekt vorreader.read(...):Risiken beschreibt die Dokumentation der jadice document platform unter PDF-Struktur Lesestrategien.PDFStructureReaderSettings settings = reader.getSettings(PDFStructureReaderSettings.class);
settings.setPDFStructureReadStrategy(PDFStructureReadStrategy.LENIENT_ON_ERROR); - Eingebettete MO:DCA-Annotationen in AFP-Dokumenten anzeigen:
jadice.viewer.afp-show-inline-annotation=true