On some project we had a Java program that needed to parse a lot of small XML messages continuously that uses a lot of memory.
After we made a heap dump and analyzed it we found the problem, a big chunk of the memory was used by Strings from the JAXB Objects. We had over 1000 times the string “de-de”.
So we did a small optimization and reduced the memory usage by 30%.
The solution was to register an XMLAdapter
for String
objects, that ensured that if a string occurs multiple times only on String instances is used and shared.
Here is a (simplified) implementation:
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;
}
}
For production use, the STRING_CACHE should probably be replaces by some other caching logic. A normal Map will never evict entries can become very big.
Now you only need to register the XMLAdapter
, for example by adding or editing the package-info.java file of the package containing the JAXB classes:
@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;
Leave a Reply