Behind the Scenes of Serialization in Java
When building distributed applications one of the central performance-critical components is serialization. Most modern frameworks make it very easy to send data over the wire. In many cases you don’t see at all what is going on behind the scenes. Choosing the right serialization strategy however is central for achieving good performance and scalability. Serialization problems affect CPU, memory, network load and response times.
Java provides us with a large variety of serialization technologies. The actual amount of data which is sent over the wire can vary substantially. We will use a very simple sample where we send the firstname, lastname and birthdate over the wire. Then we’ll see how big the actual payload gets.
As a reference we start by sending only the payload. This is the most efficient way of sending data, as there is no overhead involved. The downside is that due to the missing metadata the message can only be de-serialized if we know the exact serialization method. This approach also has a very high testing and maintenance effort and we have to handle all implementation complexity ourselves. The figure below shows what our payload looks like in binary format.
Now we switch to standard serialization in Java. As you can see in below we are now transferring much more metadata. This data is required by the Java Runtime to rebuild the transferred object at the receiver side. Besides structural information the metadata also contains versioning information which allows communication across different versions of the same object. In reality, this feature often turns out to be harder than it initially looks. The metadata overhead in our example is rather high. This is caused by large amount of data in the GregorianCalendar Object we are using. The conclusion that Java Serialization comes with a very high overhead per se, however is not valid. Most of this metadata will be cached for subsequent invocations.
Java also provides the ability to override the Serialization behavior using the Externalizable interface. This enables us to implement a more efficient serialization strategy. In our example we could only serialize the birthdate as a long rather than a full object. The downside again is the increased effort regarding testing an maintainability
Java Serialization is by default used in RMI communication when not using IIOP as a protocol. Application server providers also offer their own serialization stacks which are more efficient than default serialization. If interoperability is not important, provider-specific implementations are the better choice.
The Java ecosystem also provides interesting alternatives to Java Serialization. A widely known one is Hessian which can be easily used with Spring. Hessian allows an easy straightforward implementation of services. Underneath it uses a binary protocol. The figure below shows our data serialized with Hessian. As you can see the transferred data is very slim. Hessian therefore provides an interesting alternative to RMI.
As JSON is a purely text-based representation it comes with a higher overhead than the previously shown serialization approaches. The advantage is that it is more lightweight than XML and it has good support for describing metadata. The Listing below shows our person object represented in JSON.
XML for sure is the standard format for exchanging data in heterogeneous systems. One nice feature of XML is the out-of-the-box support for data validation, which is especially important in integration scenarios. The amount of metadata, however, can become really high – depending on the used mapping. All data is transferred in text format by default. However the usage of CDATA tags enables us to send binary data. The listing below shows our person object in XML. As you can see the metadata overhead is quite high.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
Fast InfoSet is becoming a very interesting alternative to XML. It is more or less a lightweight version of XML, which reduces unnecessary overhead and redundancies in data. This leads to smaller data set and better serialization and deserialization performance.
When working with JAX-WS 2.0 you can enable Fast InfoSet serialization by using the @FastInfoset annotation. Web Service stacks then automatically detect whether it can be used for cross service communication using HTTP Accept headers.
When looking at data serialized using Fast InfoSet the main difference you will notice is that there are no end tags. After their first occurrence there are only referenced by an index. There are a number of other indexes for content, namespaces etc.
Data is prefixed with its length. This allows faster and more efficient parsing. Additionally binary data can avoid being serialized in base64 encoding as in an XML.
In tests with standard documents the transfer size could be shrunk down to only 20 percent of the original size and the serialization speed could be doubled. The listing below shows our person object now serialized with Fast InfoSet. For Illustration purposes I skipped the processing instructions and I used a textual representation instead of binary values. Values in curly braces refer to indexed values. Values in brackets refer to the use of an index.
The real advantage however can be seen when we look what the next address object would look like. As the listing below shows we can work mostly with index data only.
Object graphs can get quite tricky to serialize. This form of serialization is not supported by all protocols. As we need to work with reference to entities, the language used by the serialization approach must provide a proper language construct. While this is no problem in serialization formats which are used for (binary) RPC-style interactions, it is often not supported out-of-the-box by text-based protocols. XML itself, for example, supports serializing object graphs using references. The WS-I however forbids the usage of the required language construct.
If a serialization strategy does not support this feature it can lead to performance and functional problems, as entities get serialized individually for each occurrence of a reference. If we are, for example, serializing addresses which have reference to country information, this information will then be serialized for each and every address object leading to large serialization sizes.
Today there are numerous variants to serialize data in Java. While binary serialization keeps being the most efficient approach, modern text-based formats like JSON or Fast Infoset provide valid alternatives – especially when interoperability is a primary concern. Modern frameworks often allow using multiple serialization strategies at the same time. So the approach can even be selected dynamically at runtime.