NOTE
5.7 Serializable
Serialization and deserialization in Java, custom readObject/writeObject hooks, implementation flow, serialVersionUID, and serialization algorithm.
This is a historical learning note and may contain outdated or incomplete understanding.
1. Serialization and Deserialization

Serialization: save an object in memory to disk in some format, or transmit it over the network.
Deserialization: convert data saved on disk or transmitted over the network back into an object in memory according to that format.
2. How to Use It
2.1. Implement the Serializable Interface
class User implements Serializable
{
private String name;
private Integer age;
...
}
2.2. Use ObjectInputStream and ObjectOutputStream for Deserialization and Serialization
User user = new User();
user.setName("zsk");
user.setAge(24);
System.out.println(user);
ObjectOutputStream oos = null;
...try
// Serialization
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);
...
File file = new File("tempFile");
ObjectInputStream ois = null;
...try
// Deserialization
ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User) ois.readObject();
System.out.println(newUser);
...
3. How to Customize Serialization and Deserialization
Serialization: implement the writeObject method. The default behavior is ObjectOutputStream.defaultWriteObject.
Deserialization: implement the readObject method. The default behavior is ObjectInputStream.defaultReadObject.
4. How Is Serialization Implemented in Java?
You only need to implement java.io.Serializable. Let’s look at the interface:
// Empty marker interface.
public interface Serializable {
}
Serialization is transmission between memory <-> disk or network. That is I/O, so the implementation can be understood by looking at the I/O classes.
4.1. ObjectInputStream
Call stack:
readObject > readObject0 > readOrdinaryObject > readSerialData
readObject > defaultReadObject
readSerialData
private void readSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slots[i].hasData) {
if (obj == null || handles.lookupException(passHandle) != null) {
defaultReadFields(null, slotDesc); // skip field values
// A custom deserialization method exists.
} else if (slotDesc.hasReadObjectMethod()) {
ThreadDeath t = null;
boolean reset = false;
SerialCallbackContext oldContext = curContext;
if (oldContext != null)
oldContext.check();
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bin.setBlockDataMode(true);
// Invoke through reflection.
slotDesc.invokeReadObject(obj, this);
} catch (ClassNotFoundException ex) {
/*
* In most cases, the handle table has already
* propagated a CNFException to passHandle at this
* point; this mark call is included to address cases
* where the custom readObject method has cons'ed and
* thrown a new CNFException of its own.
*/
handles.markException(passHandle, ex);
} finally {
do {
try {
curContext.setUsed();
if (oldContext!= null)
oldContext.check();
curContext = oldContext;
reset = true;
} catch (ThreadDeath x) {
t = x; // defer until reset is true
}
} while (!reset);
if (t != null)
throw t;
}
/*
* defaultDataEnd may have been set indirectly by custom
* readObject() method when calling defaultReadObject() or
* readFields(); clear it to restore normal read behavior.
*/
defaultDataEnd = false;
} else {
defaultReadFields(obj, slotDesc);
}
if (slotDesc.hasWriteObjectData()) {
skipCustomData();
} else {
bin.setBlockDataMode(false);
}
} else {
if (obj != null &&
slotDesc.hasReadObjectNoDataMethod() &&
handles.lookupException(passHandle) == null)
{
slotDesc.invokeReadObjectNoData(obj);
}
}
}
}
4.2. ObjectOutputStream
Call stack:
writeObject > writeObject0 (throws an exception if Serializable is not implemented) > writeOrdinaryObject > writeSerialData > invokeWriteObject (invokes a custom serialization method)
writeObject > defaultWriteObject
writeObject0
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
// Serializable is implemented.
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
// Otherwise throw an exception.
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
// If writeObject is customized.
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
// Invoke through reflection.
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
// Otherwise use default field serialization.
defaultWriteFields(obj, slotDesc);
}
}
}
5. Summary
- Serialization focuses on instance data, not class
staticvariables. serialVersionUID- This field is used to verify version consistency.
- During deserialization, the JVM compares the
serialVersionUIDin the incoming byte stream with theserialVersionUIDof the corresponding local class. - If they are equal, the versions are considered compatible and deserialization can proceed.
- Otherwise, a serialization-version mismatch exception occurs.
- Serialization algorithm
- Output the class metadata related to the object instance.
- Recursively output superclass descriptions until there are no more superclasses.
- After class metadata is written, start with the highest superclass and output the actual data values of the object instance.
- Recursively output instance data from top to bottom.
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub