Poor JAXB performance on "generic" use case - better design?
I've tried porting some Apache based XML serialization code to JAXB with very poor results. Is there any advice for best practice with this design pattern (or for ditching JAXB with this ...)?
XML relies primarily on interfaces and reflective declarations.
<someContainer>
<someChild class="foo.Class1">
</someChild>
</someContainer>
I have allowed indirect use with this non-marching code
public ResultType unmarshal(Object object) throws Exception {
Element element = (Element) object;
String classname = element.getAttribute("class");
Class clazz = Class.forName(classname);
JAXBContext jc = getContext(clazz);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object result = unmarshaller.unmarshal(element, clazz);
if (result instanceof JAXBElement) {
return (ResultType) ((JAXBElement) result).getValue();
}
return (ResultType) result;
}
Well, even with JAXBContext caching, this is far superior to digest based code (without an exact answer to this question, say 50 to 100 times). The main cause of the actual performance loss appears to be the item parameter for the unmarshal. Most of the time is spent creating a new DocumentBuilder to re-transform an already parsed element
Any advice?
source to share
Well, I'll add this for reference to other people interested in the need to ditch JAXB.
Adding
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
did this job. The performance is now in the same order of magnitude. We seem to have a simple classpath problem. In the case of a large path class, finding the service provider required for the (uncached) DocumentBuilderFactory was a key contributor to poor performance. Give the VM a known implementation to avoid searching ....
I tried this after "stochastic" profiling (the press pauses whenever you like :-) always hit the search sequence, for example:
Thread [main] (Suspended)
owns: VirtualKeyStoreHolder (id=417)
owns: CertificateStoreEnvironment (id=418)
owns: ManagedCertificateProvider (id=419)
owns: CertificateStoreEnvironment$1 (id=420)
WinNTFileSystem.getBooleanAttributes(File) line: not available [native method]
File.exists() line: 733
URLClassPath$FileLoader.getResource(String, boolean) line: 999
URLClassPath$FileLoader.findResource(String, boolean) line: 966
URLClassPath.findResource(String, boolean) line: 146
URLClassLoader$2.run() line: 385
AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]
Launcher$AppClassLoader(URLClassLoader).findResource(String) line: 382
Launcher$AppClassLoader(ClassLoader).getResource(String) line: 1003
Launcher$AppClassLoader(ClassLoader).getResourceAsStream(String) line: 1193
SecuritySupport$4.run() line: 96
AccessController.doPrivileged(PrivilegedAction<T>) line: not available [native method]
SecuritySupport.getResourceAsStream(ClassLoader, String) line: 89
FactoryFinder.findJarServiceProvider(String) line: 250
FactoryFinder.find(String, String) line: 223
DocumentBuilderFactory.newInstance() line: 123
TransformerIdentityImpl.createResultContentHandler(Result) line: 215
TransformerIdentityImpl.setDocumentLocator(Locator) line: 881
DomLoader$State.<init>(DomLoader, UnmarshallingContext) line: 78
DomLoader<ResultT>.startElement(UnmarshallingContext$State, TagName) line: 113
XsiTypeLoader.startElement(UnmarshallingContext$State, TagName) line: 76
UnmarshallingContext._startElement(TagName) line: 481
UnmarshallingContext.startElement(TagName) line: 459
SAXConnector.startElement(String, String, String, Attributes) line: 148
SAXParserImpl$JAXPSAXParser(AbstractSAXParser).startElement(QName, XMLAttributes, Augmentations) line: 501
XMLNSDocumentScannerImpl.scanStartElement() line: 400
XMLNSDocumentScannerImpl$NSContentDriver(XMLDocumentFragmentScannerImpl$FragmentContentDriver).next() line: 2755
XMLNSDocumentScannerImpl(XMLDocumentScannerImpl).next() line: 648
XMLNSDocumentScannerImpl.next() line: 140
XMLNSDocumentScannerImpl(XMLDocumentFragmentScannerImpl).scanDocument(boolean) line: 511
XIncludeAwareParserConfiguration(XML11Configuration).parse(boolean) line: 808
XIncludeAwareParserConfiguration(XML11Configuration).parse(XMLInputSource) line: 737
SAXParserImpl$JAXPSAXParser(XMLParser).parse(XMLInputSource) line: 119
SAXParserImpl$JAXPSAXParser(AbstractSAXParser).parse(InputSource) line: 1205
SAXParserImpl$JAXPSAXParser.parse(InputSource) line: 522
UnmarshallerImpl.unmarshal0(XMLReader, InputSource, JaxBeanInfo) line: 211
UnmarshallerImpl.unmarshal(XMLReader, InputSource) line: 184
UnmarshallerImpl(AbstractUnmarshallerImpl).unmarshal(InputSource) line: 137
UnmarshallerImpl(AbstractUnmarshallerImpl).unmarshal(InputStream) line: 184
VirtualKeyStoreTools.createVirtualKeyStore(InputStream) line: 54
... more to come
As there is pending bounty, I will suggest this @Osw (when the bounty can be served) as a "Yes, you can JAXB" answer correctly. I think that minor performance differences do not justify using a SAX solution plan or no longer work with a digester. Also, JAXBContext caching seems wise, I can't say about unmarshaller context caching - maybe someone is adding information about this.
Thanks for your support.
EDIT
As requested by @Osw, here are some (disappointing) numbers for overall performance.
I've made some dirty tools for the old and new app directly around parsing. While the numbers are "acceptable" for an interactive application downloading a bunch of files, I have to admit that the digester is still more than 2 * faster.
- Digester-based file ~ 1 MB containing 4000 total records = 400ms
- JAXB, see above, 900 ms
The JAXB implementation already contains a cache for the unmarshaller, so an optimization is done out of the box. This leaves me with a functional application that urgently needs additional profiling. I'll be back when it's done and some interesting tricks of general interest come up.
source to share
I do a lot of things like this and cannot complain about the poor JAXB performance which is of utmost importance in my case. The exact same task takes no more than 3-4ms for a 5k payload on a rather slow server. As far as caching is concerned, I usually create the following bi-directional data structure for marshaling / unmarshalling, which is accepted for your case:
Map<String, MarshallData> marshalCache;
public ResultType unmarshal(Object object) throws Exception {
Element element = (Element) object;
MarshallData md = marshalCache.get(element.getAttribute("class"));
Object result = md.unmarshaller.unmarshal(element);
if (result instanceof JAXBElement) {
return (ResultType) ((JAXBElement) result).getValue();
}
return (ResultType) result;
}
public void registerMarshallData(Class clazz) throws Exception {
JAXBContext jbc = JAXBContext.newInstance(clazz); // or get it somewhere else if needed
MarshallData mdata = new MarshallData(jbc.createMarshaller(), jbc.createUnmarshaller());
marshalCache.put(clazz.getName(), mdata);
}
class MarshallData {
private Unmarshaller unmarshaller;
private Marshaller marshaller;
protected MarshallData(Marshaller marshaller, Unmarshaller unmarshaller) {
this.marshaller = marshaller;
this.unmarshaller = unmarshaller;
}
}
source to share