使用kryo的一些问题

2017-08-27 11:13:54

kryo经常可以在网上看到这样的例子

    private static Kryo kryo = new Kryo();

    @Override
    public byte[] serialize(Object o) {
        Output output = new Output(new ByteArrayOutputStream());
        kryo.writeClassAndObject(output, o);

        byte[] bytes = output.toBytes();

        output.flush();
        output.close();

        return bytes;
    }

    @Override
    public <T> T deserialize(byte[] bytes) {
        Input input = new Input(bytes);
        T o = (T)kryo.readClassAndObject(input);
        return o;
    }

但这样实际有两个隐藏的问题。

  • Output output = new Output(new ByteArrayOutputStream());会导致缓冲区溢出
    public Output(OutputStream outputStream) {
        this(4096, 4096);
        if(outputStream == null) {
            throw new IllegalArgumentException("outputStream cannot be null.");
        } else {
            this.outputStream = outputStream;
        }
    }

可以看到使用OutputStream构建Output时,缓冲区默认值和最大值都是4096。当遇到大对象,会缓冲区溢出,这是kryo会从开头重新写缓冲区,这样又导致另一个问题。

kryo中序列化的类要注册一个id,kryo序列化时,会将该id+2写入结果开头,作为class的标识。
kryo还提供了writeClassAndObject/readClassAndObject的方式,会将class的信息写入到结果字节中,读取时也会从字节中读取class信息,而class标识为1,表示使用的是writeClassAndObject/readClassAndObject的方式。

回到缓冲区溢出的问题,kryo从开头重新写缓冲区,会导致class标识被重写为其他值,当读取时,kryo会使用该错误的class标识查找对应的注册class,查找失败会抛出class未注册的异常:

com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: -39

回到缓冲区溢出导致class未注册的异常,这个就非常有迷惑性了,需要对kryo机制有一定了解才能解决。

  • private static Kryo kryo = new Kryo();导致线程安全问题

Kryo不是线程安全的,使用该静态的Kryo类会在多线程情况出现各种问题。
为了避免频繁创建Kryo对象导致性能损耗,可以使用ThreadLocal或apache的commons-pool2连接池。

kryo是性能较高的序列化工具,可用于dubbo或netty的序列化性能,拓展:
使用Kryo的序列化方式提升Netty性能

参考:
Kryo简介及代码阅读笔记
kryo序列化:默认bufferSize