Speicher-Optimierung beim JAXB parsen
9. Februar 2024Bei einem Projekt hatten wir einen hohen Speicherverbrauch bei einem Java Programm, dass viele XML-Nachrichten kontinuierlich liest und mit JAXB zu Java Objekten umwandelt.
Nachdem wir einen Heap Dump erstellt und analysiert hatten, stellten wir fest, dass ein Großteil des Speichers mit Strings aus den JAXB Objekten belegt war und dass sehr viele Strings mehrmals vorhanden war. So hatten wir z.B. über 1000 Mal den String “de-DE”.
Durch eine kleine Optimierung konnten wir das verbessern und den Speicherverbrauch um ein Drittel reduzieren.
Die Lösung war, einen XMLAdapter
für Strings zu registrieren, der dafür sorgt, dass wenn ein String mehrmals vorkommt, immer dieselbe Instanz zurückgegeben wird. Dadurch haben wir nur noch eine Instanz den Strings “de-DE” der aber von vielen JAXB Objekten gleichzeitig benutzt wird.
Hier eine beispielhafte Implementierung:
package de.nilsrudolph.blog;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public class CachingXmlAdapter extends XmlAdapter<String, String> {
private static final ConcurrentHashMap<String, String> STRING_CACHE = new ConcurrentHashMap<>();
@Override
public String unmarshal(String s) {
return STRING_CACHE.computeIfAbsent(s, Function.identity());
}
@Override
public String marshal(String s) {
return s;
}
}
Je nach Anwendungsfall sollte man sich über die Art des Caches Gedanken machen. In dem Beispiel wird einfach eine ConcurrentHashMap
verwendet, die nie geleert wird und damit auch beliebig groß werden kann.
Nun muss man den XmlAdapter
noch bei JAXB registrieren, das geht z.B. über einen package weiten XMLAdapter den man in der package-info.java des jeweiligen packages registrien kann:
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value = CachingXmlAdapter.class, type = String.class)
})
package de.nilsrudolph.blog;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters;