Der Java-Serialisierungsalgorithmus wurde enthüllt

Bei der Serialisierung wird der Status eines Objekts in einer Folge von Bytes gespeichert. Bei der Deserialisierung werden diese Bytes in ein lebendes Objekt umgewandelt. Die Java-Serialisierungs-API bietet Entwicklern einen Standardmechanismus für die Objekt-Serialisierung. In diesem Tipp erfahren Sie, wie Sie ein Objekt serialisieren und warum manchmal eine Serialisierung erforderlich ist. Sie lernen den in Java verwendeten Serialisierungsalgorithmus kennen und sehen ein Beispiel, das das serialisierte Format eines Objekts veranschaulicht. Bis Sie fertig sind, sollten Sie über fundierte Kenntnisse darüber verfügen, wie der Serialisierungsalgorithmus funktioniert und welche Entitäten als Teil des Objekts auf niedriger Ebene serialisiert werden.

Warum ist eine Serialisierung erforderlich?

In der heutigen Welt besteht eine typische Unternehmensanwendung aus mehreren Komponenten und wird auf verschiedene Systeme und Netzwerke verteilt. In Java wird alles als Objekte dargestellt. Wenn zwei Java-Komponenten miteinander kommunizieren möchten, muss ein Mechanismus zum Datenaustausch vorhanden sein. Eine Möglichkeit, dies zu erreichen, besteht darin, ein eigenes Protokoll zu definieren und ein Objekt zu übertragen. Dies bedeutet, dass das empfangende Ende das Protokoll kennen muss, das vom Absender zum erneuten Erstellen des Objekts verwendet wird, was es sehr schwierig machen würde, mit Komponenten von Drittanbietern zu kommunizieren. Daher muss es ein generisches und effizientes Protokoll geben, um das Objekt zwischen Komponenten zu übertragen. Zu diesem Zweck wird die Serialisierung definiert, und Java-Komponenten verwenden dieses Protokoll zum Übertragen von Objekten.

Abbildung 1 zeigt eine allgemeine Ansicht der Client / Server-Kommunikation, bei der ein Objekt durch Serialisierung vom Client zum Server übertragen wird.

Abbildung 1. Eine allgemeine Ansicht der Serialisierung in Aktion (zum Vergrößern anklicken)

So serialisieren Sie ein Objekt

Um ein Objekt zu serialisieren, müssen Sie sicherstellen, dass die Klasse des Objekts die java.io.SerializableSchnittstelle implementiert , wie in Listing 1 gezeigt.

Listing 1. Serializable implementieren

 import java.io.Serializable; class TestSerial implements Serializable { public byte version = 100; public byte count = 0; } 

In Listing 1 mussten Sie lediglich die java.io.SerializableSchnittstelle implementieren, um eine normale Klasse zu erstellen. Die SerializableSchnittstelle ist eine Markierungsschnittstelle; es deklariert überhaupt keine Methoden. Es teilt dem Serialisierungsmechanismus mit, dass die Klasse serialisiert werden kann.

Nachdem Sie die Klasse für die Serialisierung zugelassen haben, besteht der nächste Schritt darin, das Objekt tatsächlich zu serialisieren. Dies erfolgt durch Aufrufen der writeObject()Methode der java.io.ObjectOutputStreamKlasse, wie in Listing 2 gezeigt.

Listing 2. Aufruf von writeObject ()

 public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); TestSerial ts = new TestSerial(); oos.writeObject(ts); oos.flush(); oos.close(); } 

Listing 2 speichert den Status des TestSerialObjekts in einer aufgerufenen Datei temp.out. oos.writeObject(ts);startet tatsächlich den Serialisierungsalgorithmus, in den das Objekt wiederum geschrieben wird temp.out.

Um das Objekt aus der persistenten Datei neu zu erstellen, verwenden Sie den Code in Listing 3.

Listing 3. Erstellen eines serialisierten Objekts

 public static void main(String args[]) throws IOException { FileInputStream fis = new FileInputStream("temp.out"); ObjectInputStream oin = new ObjectInputStream(fis); TestSerial ts = (TestSerial) oin.readObject(); System.out.println("version="+ts.version); } 

In Listing 3 erfolgt die Wiederherstellung des Objekts mit dem oin.readObject()Methodenaufruf. Dieser Methodenaufruf liest die zuvor beibehaltenen Rohbytes ein und erstellt ein lebendes Objekt, das eine exakte Nachbildung des ursprünglichen Objektdiagramms ist. Da readObject()jedes serialisierbare Objekt gelesen werden kann, ist eine Umwandlung in den richtigen Typ erforderlich.

Wenn Sie diesen Code ausführen, wird version=100die Standardausgabe gedruckt .

Das serialisierte Format eines Objekts

Wie sieht die serialisierte Version des Objekts aus? Denken Sie daran, dass der Beispielcode im vorherigen Abschnitt die serialisierte Version des TestSerialObjekts in der Datei gespeichert hat temp.out. Listing 4 zeigt den Inhalt von temp.out, hexadezimal angezeigt. (Sie benötigen einen Hexadezimal-Editor, um die Ausgabe im Hexadezimalformat anzuzeigen.)

Listing 4. Hexadezimale Form von TestSerial

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05 63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78 70 00 64 

Wenn Sie sich das eigentliche TestSerialObjekt noch einmal ansehen , werden Sie feststellen, dass es nur zwei Byte-Mitglieder hat, wie in Listing 5 gezeigt.

Listing 5. Die Byte-Mitglieder von TestSerial

 public byte version = 100; public byte count = 0; 

Die Größe einer Bytevariablen beträgt ein Byte, und daher beträgt die Gesamtgröße des Objekts (ohne Header) zwei Bytes. Wenn Sie sich jedoch die Größe des serialisierten Objekts in Listing 4 ansehen, sehen Sie 51 Bytes. Überraschung! Woher kamen die zusätzlichen Bytes und welche Bedeutung haben sie? Sie werden vom Serialisierungsalgorithmus eingeführt und sind erforderlich, um das Objekt neu zu erstellen. Im nächsten Abschnitt werden Sie diesen Algorithmus im Detail untersuchen.

Javas Serialisierungsalgorithmus

Inzwischen sollten Sie ziemlich gute Kenntnisse darüber haben, wie ein Objekt serialisiert wird. Aber wie funktioniert der Prozess unter der Haube? Im Allgemeinen führt der Serialisierungsalgorithmus Folgendes aus:

  • Es schreibt die Metadaten der einer Instanz zugeordneten Klasse aus.
  • Es schreibt rekursiv die Beschreibung der Oberklasse aus, bis sie gefunden wird java.lang.object.
  • Sobald das Schreiben der Metadateninformationen abgeschlossen ist, beginnt es mit den tatsächlichen Daten, die der Instanz zugeordnet sind. Aber diesmal geht es von der obersten Oberklasse aus.
  • Es schreibt rekursiv die der Instanz zugeordneten Daten, beginnend von der kleinsten Oberklasse bis zur am meisten abgeleiteten Klasse.

Ich habe für diesen Abschnitt ein anderes Beispielobjekt geschrieben, das alle möglichen Fälle abdeckt. Das neue zu serialisierende Beispielobjekt ist in Listing 6 dargestellt.

Listing 6. Beispiel für ein serialisiertes Objekt

 class parent implements Serializable { int parentVersion = 10; } class contain implements Serializable{ int containVersion = 11; } public class SerialTest extends parent implements Serializable { int version = 66; contain con = new contain(); public int getVersion() { return version; } public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); SerialTest st = new SerialTest(); oos.writeObject(st); oos.flush(); oos.close(); } } 

This example is a straightforward one. It serializes an object of type SerialTest, which is derived from parent and has a container object, contain. The serialized format of this object is shown in Listing 7.

Listing 7. Serialized form of sample object

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07 76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09 4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72 65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00 0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70 00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74 61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00 0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78 70 00 00 00 0B 

Figure 2 offers a high-level look at the serialization algorithm for this scenario.

Figure 2. An outline of the serialization algorithm

Let's go through the serialized format of the object in detail and see what each byte represents. Begin with the serialization protocol information:

  • AC ED: STREAM_MAGIC. Specifies that this is a serialization protocol.
  • 00 05: STREAM_VERSION. The serialization version.
  • 0x73: TC_OBJECT. Specifies that this is a new Object.

The first step of the serialization algorithm is to write the description of the class associated with an instance. The example serializes an object of type SerialTest, so the algorithm starts by writing the description of the SerialTest class.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 0A: Length of the class name.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest, the name of the class.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This particular flag says that the object supports serialization.
  • 00 02: Number of fields in this class.

Next, the algorithm writes the field int version = 66;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 07: Length of the field name.
  • 76 65 72 73 69 6F 6E: version, the name of the field.

And then the algorithm writes the next field, contain con = new contain();. This is an object, so it will write the canonical JVM signature of this field.

  • 0x74: TC_STRING. Represents a new string.
  • 00 09: Length of the string.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, the canonical JVM signature.
  • 0x78: TC_ENDBLOCKDATA, the end of the optional block data for an object.

The next step of the algorithm is to write the description of the parent class, which is the immediate superclass of SerialTest.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 06: Length of the class name.
  • 70 61 72 65 6E 74: SerialTest, the name of the class
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag notes that the object supports serialization.
  • 00 01: Number of fields in this class.

Now the algorithm will write the field description for the parent class. parent has one field, int parentVersion = 100;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0D: Length of the field name.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA, the end of block data for this object.
  • 0x70: TC_NULL, which represents the fact that there are no more superclasses because we have reached the top of the class hierarchy.

So far, the serialization algorithm has written the description of the class associated with the instance and all its superclasses. Next, it will write the actual data associated with the instance. It writes the parent class members first:

  • 00 00 00 0A: 10, the value of parentVersion.

Then it moves on to SerialTest.

  • 00 00 00 42: 66, the value of version.

The next few bytes are interesting. The algorithm needs to write the information about the contain object, shown in Listing 8.

Listing 8. The contain object

 contain con = new contain(); 

Remember, the serialization algorithm hasn't written the class description for the contain class yet. This is the opportunity to write this description.

  • 0x73: TC_OBJECT, designating a new object.
  • 0x72: TC_CLASSDESC.
  • 00 07: Length of the class name.
  • 63 6F 6E 74 61 69 6E: contain, the name of the class.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag indicates that this class supports serialization.
  • 00 01: Number of fields in this class.

Next, the algorithm must write the description for contain's only field, int containVersion = 11;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0E: Length of the field name.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA.

Next, the serialization algorithm checks to see if contain has any parent classes. If it did, the algorithm would start writing that class; but in this case there is no superclass for contain, so the algorithm writes TC_NULL.

  • 0x70: TC_NULL.

Finally, the algorithm writes the actual data associated with contain.

  • 00 00 00 0B: 11, the value of containVersion.

Conclusion

In this tip, you have seen how to serialize an object, and learned how the serialization algorithm works in detail. I hope this article gives you more detail on what happens when you actually serialize an object.

About the author

Sathiskumar Palaniappan verfügt über mehr als vier Jahre Erfahrung in der IT-Branche und arbeitet seit mehr als drei Jahren mit Java-bezogenen Technologien. Derzeit arbeitet er als System-Software-Ingenieur im Java Technology Center von IBM Labs. Er hat auch Erfahrung in der Telekommunikationsbranche.

Ressourcen

  • Lesen Sie die Java-Objektserialisierungsspezifikation. (Spezifikation ist ein PDF.)
  • "Reduzieren Sie Ihre Objekte: Entdecken Sie die Geheimnisse der Java-Serialisierungs-API" (Todd M. Greanier, JavaWorld, Juli 2000) bietet einen Einblick in die Grundlagen des Serialisierungsprozesses.
  • Kapitel 10 von Java RMI (William Grosso, O'Reilly, Oktober 2001) ist ebenfalls eine nützliche Referenz.

Diese Geschichte "Der offenbarte Java-Serialisierungsalgorithmus" wurde ursprünglich von JavaWorld veröffentlicht.