How does Deserialization happen in Java?

Deserialization is the process by which the object previously serialized is reconstructed back into its original form i.e. object instance. The input to the deserialization process is the stream of bytes that we get over the other end of the network OR we simply read it from the filesystem/database. One question arises immediately, what is written inside this stream of bytes?

Read More: Guide for Implementing Serializable Interface

1. Stream Content

To be very precise, this stream of bytes (or say serialized data) has all the information about the instance which was serialized by the serialization process. This information includes the class’s metadata, type information of instance fields and values of instance fields as well. This same information is needed when the object is reconstructed into a new object instance.

While deserializing an object, the JVM reads its class metadata from the stream of bytes which specifies whether the class of an object implements either Serializable or Externalizable interface.

Please note that for the deserialization to happen seamlessly, the bytecode of a class, whose object is being deserialized, must be present within the JVM performing deserialization. Otherwise, the ‘ClassNotFoundException’ is thrown. Isn’t it too obvious ??

2. Initialization without Constructor

If the instance implements the Serializable interface, then an instance of the class is created without invoking its constructor. Really? then how is the object created if no constructor is called?

Let’s look at the bytecode of a simple program:

public class SimpleProgram
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}

The generated bytecode is:

public class SimpleProgram extends java.lang.Object{
public SimpleProgram();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String Hello World!
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:	return
}

In our first line, we will push a value from the “local variable table” into the stack memory. In this case, we’re really only pushing the implicit reference to “this” so it isn’t the most exciting instruction.

The second instruction is the main thing. It actually invokes the constructor of super most class and in the above case it is Object class. And once the constructor of super most class (i.e. Object in this case) has been called, the rest of the code does specific instructions written in the code.

Matching to the above concept i.e. constructor of the super most class, we have a similar concept in deserialization.

In the deserialization process, it is required that all the parent classes of instance should be Serializable; and if any super-class in the hierarchy is not Serializable then it must have a default constructor. Now it makes sense. So, while deserialization the super most class is searched first until any non-serializable class is found.

If all superclasses are serializable then JVM ends up reaching Object class itself and create an instance of Object class first. If in between searching the superclasses, any class is found non-serializable then its default constructor will be used to allocate an instance in memory.

If any super class of instance to be de-serialized in non-serializable and also does not have a default constructor then the ‘NotSerializableException‘ is thrown by JVM.

Also, before continuing with the object reconstruction, the JVM checks to see if the SerialVersionUID mentioned in the byte stream matches the serialVersionUID of the class of that object. If it does not match then the ‘InvalidClassException‘ is thrown.

3. Post Initialization

So till now, we got the instance located in memory using one of the superclass’s default constructors. Note that after this no constructor will be called for any class. After executing the superclass constructor, JVM read the byte stream and uses the instance’s metadata to set type information and other meta information of the instance.

After the blank instance is created, JVM first set its static fields and then invokes the default readObject() method [if it’s not overridden, otherwise overridden method will be called] internally which is responsible for setting the values from byte stream to blank instance.

Read More: Example code for readObject() and writeObject()

After the readObject() method is completed, the deserialization process is done and you are ready to work with the new deserialized instance.

Happy Learning !!

Comments

Subscribe
Notify of
guest
31 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode