反序列化中对象实例化的原理探究

前言:

​ 最近打算将知识点学的细致一点,之前分析过java原生的序列化和反序列化流程,还算是比较的细;但是今天想了一个问题,在反序列化的时候如果目标类继承Serializable,那么在反序列化的时候不会触发其构造函数;当然我们知道,在java中实例化对象的方式一般有五种;

  1. new object
  2. Class.newinstance
  3. Constructor.newinstance
  4. Unsafe
  5. 反序列化创建对象

今天着重探究一下反序列化创建对象的方式;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.*;
class SU implements Serializable{
public int idcard;
public String school;
public SU() {
this.idcard=410881;
this.school="ncu";
}
}
class information extends SU implements Serializable {
public String id = "s1mple";
public int num = 20;
public String hacker = "hack";


public information() {
System.out.println("success");
}
public void tests(){
System.out.println("test");
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

in.defaultReadObject();

System.out.println("fuckerfucker");
}
}
public class cmd {
public static void main(String args[]) throws Exception {
information test = new information();
ObjectInputStream s2mple = new ObjectInputStream(new FileInputStream("test.ser"));
information cc = (information) s2mple.readObject();
System.out.println(cc.id);
}
}

简单来看一个demo;

之前文章讲述过,ObjectStreamClass函数对一个class进行内省并且封装,里面有一个敏感的函数用来获取constructor;

LV8iLD.png

追踪进去这个函数内部看一下逻辑;

LV8uSP.png

内部实现了reflFactory.newConstructorForSerialization;下面就对其进行一个分析;

其实这里就是网上广泛说的,先拿到非继承Serializable类的构造函数然后再基于此生成新的construcotr;可以看一下上面图中传入的参数就可,cl和cons;

跟进去看一下内部的实现逻辑;

LVGj81.png

可以看到首先进行了constructor判断,判断当前的构造器是否为需要实例化对象的构造器,这里显然不是,这也是由反序列化中的逻辑决定的,会先进行“上”溯;拿到非继承Serializable类的构造函数;这里直接去到基类里了;判断不是之后就会进入到generateConstructor的逻辑;很显然就要创造一个新的构造器;这一步主要是因为:Constructor 对象中有一个关键属性 private volatile ConstructorAccessor constructorAccessor; ,ConstructorAccessor 是一个接口 如果构造方法不存在是没有办法反射生成的,reflectionFactory.newConstructorForSerialization 使用字节码的方式生成了这个接口的实现类。下面就来简单理解一下:

LVt4gI.png

看一下实现的源码,先去实例化一个MethodAccessorGenerator之后调用generateSerializationConstructor函数:

LVNhoF.png

看一下逻辑不难理解;传入目标的参数,从而去创建一个constructor;上面已经说过相关的原理了,采用字节码的方式进行生成;

LVUeYQ.png

具体的实现逻辑在generate函数中实现;中间的一些实现逻辑简单跳过;最后生成点是在下面:

LVUTAS.png

这里的byte就是最后的字节码,其相关的过程就是上面省略的那一部分;不过可以向上简单看一下最后的转化点:

LVaL5D.png

最开始的传入点:

LVDTA0.png

期间add或者set一些东西:

LVDXjJ.png

最后导出字节码,然后defineclass进行加载的操作;

LVrR56.png

这里看一下相关的逻辑,有一个需要去注意的点,在defineclass的时候,每次加载的时候有可能是不同的Classloader declaringClass.getClassLoader()).newInstance();所以如果一直大量的使用,不对Constructor 对象进行缓存,会不停的加载类 最终导致 Metaspace 空间不足,可能会频繁的触发 FullGC 的情况。

最后进行defineclass之后生成Class对象,Name为

LVywX4.png

最后返回ConstructorAccessor接口的实现类;

LVcGWV.png

然后通过setConstructorAccessor进行set;然后返回;

到此相比就很明白为什么之后尽管调用Object的newinstance也会实例化information的实例了吧;

回看一下Constructor这个class下的newInstance方法;

LVRIYR.png

原理已经很透彻了;感兴趣的师傅可以进一步分析一下Constructor这个class下的实现逻辑,就会更加的明白相关的原理;