Nils Rudolph
Dem Ingeniör ist nichts zu schwör! - Daniel Düsentrieb
Über mich

Speicher-Optimierung beim JAXB parsen

9. Februar 2024

Bei 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;