8/07/2011

My notes on serialization and de-serialization

I wrote the following test to check some aspects of java serialization and de-serialization, followed by some notes:

package test;
import java.io.*;
import java.util.logging.Logger;

public class SerializableTest implements java.io.Serializable {

private static final long serialVersionUID = 0L;
private static final String fileName = "Serializable.ser";
private static final Logger logger = Logger.getLogger("");
private static String var;
transient private String tran = "this is a transient instance field";
private int number;
private Thread th;

public static void main(String[] args) throws IOException, ClassNotFoundException {
if (args[0].startsWith("ser")) {
SerializableTest test = new SerializableTest();
test.number = 1;
var = "this is a static variable";
writeOut(test);
System.out.println("SerializableTest to be saved: " + test);
} else if (args[0].startsWith("deser")) {
System.out.println("SerializableTest deserialized: " + readIn());
}
}

private static Object readIn() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)));
return ois.readObject();
}

private static void writeOut(java.io.Serializable obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName)));
oos.writeObject(obj);
oos.close();
}

@Override public String toString() {
return "SerializableTest: final static fileName=" + fileName + ", final static logger=" + logger
+ ", non-final static var=" + var + ", instance number=" + number
+ ", transient instance tran=" + tran + ", non-serializable instance field:=" + th;
}
}
To run the program to serialize an instance to current directory:
$ java test.SerializableTest ser

SerializableTest to be saved: SerializableTest:
final static fileName=Serializable.ser,
final static logger=java.util.logging.LogManager$RootLogger@43ef9157,
non-final static var=this is a static variable,
instance number=1,
transient instance tran=this is a transient instance field
The output dumps the in-memory state of the object, before writing it to disk. To show the file type of the data file containing the object:
$ file Serializable.ser
Serializable.ser: Java serialization data, version 5
To de-serialize, or re-constitute the object from the data file:
$ java test.SerializableTest deser

SerializableTest deserialized:
SerializableTest: final static fileName=Serializable.ser,
final static logger=java.util.logging.LogManager$RootLogger@252f0999,
non-final static var=null,
instance number=1,
transient instance tran=null
The output shows the object state reconstructed from the data file.

(1) transient and static fields are ignored in serialization. After de-serialization transient fields and non-final static fields will be null. Final static fields still have values since they are part of the class data.

(2) ObjectOutputStream.writeObject(obj) and ObjectInputStream.readObject() are used in serialization and de-serialization, not BufferedOutputStream/BufferedInputStream. There is no BufferedObjectOutputStream or BufferedObjectInputStream.

(3) During serialization, you need to handle IOException; during de-serialization, you need to handle IOException and ClassNotFoundException. So the transitive closure of the de-serializaed type must be in the classpath on the receiving side.

(4) Some non-serializable classes: Thread, Logger, LogManager and most classes in java.util.logging (Level and LogRecord are serializable, though). Throwable extends Serializable, so all Error and Exception types are serializable via inheritance.

(5) Uninitialized non-serializable, non-transient instance fields are tolerated. When adding "private Thread th;", no error in serializable. However, "private Thread th = new Thread();" will cause exception:
Exception in thread "main" java.io.NotSerializableException: java.lang.Thread
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
at test.SerializableTest.writeOut(SerializableTest.java:33)
at test.SerializableTest.main(SerializableTest.java:19)
(6) It is possible to include static fields in serialization and de-serialization, by implementing writeObject and readObject methods. These 2 methods are not in java.io.Serialiable, which is just a marker interface, and not in java.lang.Object, either. They are just 2 magic methods with pre-defined signatures. The following is from Technical Article: Advanced Serialization
private void writeObject(ObjectOutputStream oos) 
throws IOException {
oos.defaultWriteObject();
// Write/save additional fields
oos.writeObject(new java.util.Date());
}

// assumes "static java.util.Date aDate;" declared
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
// Read/initialize additional fields
aDate = (java.util.Date)ois.readObject();
}
For a definitive guide, refer to Official Java Object Serialization Specification version 6.0

(7) Serialization and de-serialization can be used for copying and cloning objects. It is slower than regular clone, but can produce a deep copy very easily. The following util method is adapted from Secrets of Java Serialization:
    public static Object clone(Object o) 
throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(o);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object copy = ois.readObject();
ois.close();
return copy;
}

(8) If I need to serialize a Serializable class Person, but one of its superclasses is not Serializable, can Person class still be serializaed and de-serialized? The answer is yes, provided that the non-serializable superclass has a no-arg constructor, which is invoked at de-serialization to initialize that superclass.

(9) How to use a custom class loader during de-serialization? Oftentimes the context class loader of the receiving end is not the right one. Then you will need to create a subclass of java.io.ObjectInputStream, overriding resolveClass method. This is one of the few occasions where you need your own i/o classes.

protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException

(10) Be careful when modifying a class implementing java.io.Serializable. If it does not contain a serialVersionUID field, its serialVersionUID will be automatically generated by the compiler. Different compilers, or different versions of the same compiler, will generate potentially different values. Usually the computation of serialVersionUID is based on not only fields, but also on other aspect of the class like implement clause, constructors, etc. So the best practice is to explicitly declare a serialVersionUID field to maintain backward compatibility. If you need to modify the serializable class substantially and expect it to be incompatible with previous versions, then you need to increment serialVersionUID to avoid mixing different versions.
private static final long serialVersionUID = 42L;
(11) The following is a symptom of a mismatched serialVersionUID:
java.io.IOException: Mismatched serialization UIDs :
The fix is to find out the previous serialVersionUID with $JAVA_HOME/bin/serialver tool, and use it as the value of serialVersionUID field in the new version of the class.

(12) 2 usages of serialization and de-serialization:
  • Temporal serialization: an object is serialized and some time later (could be years later) is restored and put to work.

  • Spatial serialization: an object is serialized and subsequently transferred to another memory space, where it is de-serialized and put to work. This is the more realistic use case than case 1. For example, product foo 1.0 works and exchanges data with product foo 2.0. Or server product A interoperates with server product B through some remoting protocol such as RMI/IIOP. A and B could share some common classes (e.g., javax.ejb.EJBException), and if some common classes do not explicitly declare serialVersionUID, product A and B may compile their own copy and end up having different serialVersionUID.

2 comments:

Anna said...

Great and Useful Article.

Online Java Training

Online Java Training from India

Online Java Training

Online Java Training From India

Java Training Institutes in Chennai

Java Training in Chennai

Vũ Diệu Linh said...

I enjoyed on reading your blog post. Very interesting to read this article.I would like to thank you for the efforts you had made for writing this awesome article. Please visit my website, Friv 4000 Games is where all the free friv games.
Friv 4000