java序列化總結(jié)

為什么需要序列化

  • 數(shù)據(jù)持久化(如session信息存儲(chǔ)到redis)或在網(wǎng)絡(luò)上傳輸(如RPC遠(yuǎn)程調(diào)用)

序列化要考慮的因素

  • 性能:速度越快越好
  • 序列化后字節(jié)大小:字節(jié)越小越好创坞,節(jié)省帶寬和存儲(chǔ)器空間
  • 兼容性:類的信息發(fā)生變化就乓,舊的序列化數(shù)據(jù)是否能正常用新類反序列化秸脱,或者反之十兢。如果序列化內(nèi)容是放在內(nèi)存并且每次發(fā)版(停服發(fā)版)都會(huì)清空洽腺,那么可以不考慮兼容武福,否則兼容性就要考慮$员#灰度發(fā)布之類的也要考慮兼容性汛闸。這里說的兼容性是指加減字段,不包括更改字段類型涮俄。

常見序列化方式

  • JDK自帶的ObjectInputStream和ObjectOutputStream蛉拙,需要實(shí)現(xiàn)Serializable,需要兼容的話要寫死servialVersionUID彻亲。性能低孕锄、體積大吮廉。
  • 各類json(jackson, gson, fastjson),性能比jdk稍高畸肆,體積也稍小宦芦,對(duì)人友好,基本所有主流語言都支持轴脐,跨語言性非常好调卑。兼容性好。但是類和字段的信息沒有序列化進(jìn)去大咱,在反序列化的時(shí)候需要指定類名恬涧。
  • hessian:性能和字節(jié)比jdk好,兼容性差碴巾。如果子類和父類有相同的屬性名溯捆,那么在反序列化后會(huì)丟失字節(jié),原因:hessian先寫子類Field厦瓢,再寫父類Field提揍,寫值的順序也一樣,因?yàn)楦割怓ield的值一般都是null煮仇,所以在反序列化的時(shí)候劳跃,總是把最后的父類的null值覆蓋掉子類的值,具體原因參考:https://www.cnblogs.com/yfyzy/p/7197679.html浙垫。hessian的一些類不是public刨仑,不能繼承,如果要改的話只能改源碼了夹姥。
  • hessian-lite:阿里dubbo項(xiàng)目里默認(rèn)用的序列化協(xié)議贸人,改自hessian,他解決了字節(jié)丟失問題佃声,就是在獲取所有Field后做下reverse操作,顛倒了Field的順序倘要。但是經(jīng)過測試發(fā)現(xiàn)heissian-lite速度太慢了圾亏,見issue:https://github.com/dubbo/hessian-lite/issues/10
  • kryo:速度和性能都很好,默認(rèn)不兼容封拧,不過通過設(shè)置CompatibleFieldSerializcer就能支持兼容志鹃,但是也不允許父類和子類有相同名字的屬性,可以通過繼承過濾掉同名屬性泽西。kryo可以參考官方文檔曹铃,https://github.com/EsotericSoftware/kryo#compatiblefieldserializer-settings,很詳細(xì)的捧杉。
  • fst:性能和字節(jié)大小都是最優(yōu)的陕见,可惜兼容性要在字段上加@Version秘血,只能增字段不能刪,對(duì)業(yè)務(wù)開發(fā)侵入太大评甜,如果不考慮兼容的話可以考慮用fst灰粮。參考:https://blog.csdn.net/dutlxq2014/article/details/86698268。wiki:https://github.com/RuedigerMoeller/fast-serialization/wiki
  • 需要靜態(tài)編譯的忍坷,如果protobuf, thrift粘舟,適合內(nèi)部系統(tǒng)之間RPC,本文不涉及這部分佩研。

kryo目前的bug

  • kryo不要每次都new Kryo()柑肴,這樣性能太差,需要用ThreadLocal或池化存儲(chǔ)kryo實(shí)例旬薯,不過目前發(fā)行版池化有個(gè)bug:https://github.com/EsotericSoftware/kryo/issues/642晰骑,每次池里取不到都會(huì)new一個(gè)出來,在還到池里的時(shí)候袍暴,如果池滿了就會(huì)拋queue full異常些侍。目前kryo池化還有一個(gè)bug,參考:https://github.com/EsotericSoftware/kryo/issues/664政模。只能自己實(shí)現(xiàn)池化岗宣。
  • 序列化后如果bean的字段改了類型會(huì)導(dǎo)致jvm crash,雖說字段改類型不應(yīng)該淋样,但是導(dǎo)致jvm crash也是一個(gè)大問題耗式。參考:https://github.com/EsotericSoftware/kryo/issues/663,能反序列化成功就是因?yàn)閺男蛄谢止?jié)里拿到原來的類型趁猴,然后通過unsafe直接寫內(nèi)存刊咳。

性能和字節(jié)大小對(duì)比

SerializeBenchmarkTest3測試類,對(duì)幾種序列化方式進(jìn)行了測試:
測試數(shù)據(jù):

private Person getPerson() {
        Person person = new Person();
        person.setId(123L);
        person.setName("你好啊");
        person.setMarried(true);
        person.setAge(22);
        person.setDigits(Arrays.asList(1L, 3L, 100L));
        Map<String, Double> scoreMap = new LinkedHashMap<>();
        scoreMap.put("chinese", 90d);
        scoreMap.put("english", 80.5d);
        person.setScores(scoreMap);
        Book book = new Book();
        book.setId(99L);
        book.setName("代碼大全");
        book.setPrice(56.00d);
        person.setBook(book);

        int friendsCount = 1000;
        List<Person> friends = new ArrayList<>(friendsCount);
        for (int i = 0; i < friendsCount; i++) {
            Person friend = new Person();
            friend.setId(Long.valueOf(i));
            friend.setName(String.valueOf("我的朋友" + i));
            friend.setMarried(i % 2 == 0 ? true : false);
            friends.add(friend);
        }
        person.setFriends(friends);
        return person;
    }

test1方法測試了序列化后自己大小和md5儡司,測試結(jié)果如下:

2019-03-23 15:22:04,916 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jdkPerformance(54) - jdk序列化后長度:52797, 前后長度一致:true, md5一致:true娱挨,對(duì)象equals:true
2019-03-23 15:22:05,226 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jsonPerformance(71) - json序列化后長度:59457, 前后長度一致:true, md5一致:true,對(duì)象equals:true
2019-03-23 15:22:05,309 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessian2Performance(88) - hessian2序列化后長度:26124, 前后長度一致:false, md5一致:false捕犬,對(duì)象equals:false
2019-03-23 15:22:05,380 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessianLitePerformance(105) - hessian-lite序列化后長度:26144, 前后長度一致:false, md5一致:false跷坝,對(duì)象equals:true
2019-03-23 15:22:05,661 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.kryoPerformance(122) - kryo序列化后長度:28101, 前后長度一致:true, md5一致:true,對(duì)象equals:true
2019-03-23 15:22:05,696 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.fstPerformance(139) - fst序列化后長度:33839, 前后長度一致:true, md5一致:true碉碉,對(duì)象equals:true

由于Person類繼承了Human類柴钻,2個(gè)類都有同名屬性id,hessian2在序列化的時(shí)候存在bug導(dǎo)致丟失數(shù)據(jù)垢粮,奇怪的是hessian-lite雖然解決了這個(gè)bug贴届,但是前后序列化字節(jié)長度卻不相等。
從上面結(jié)果可以看出,在小數(shù)據(jù)量場景下毫蚓,hessian2及hessian-lite在體積上占有小優(yōu)勢占键,kryo、fst次之绍些,jdk和json最差捞慌。

然后對(duì)上面的數(shù)據(jù)做10000次序列化和反序列化,結(jié)果如下:

14次YGC
13.658: [GC (Allocation Failure) [PSYoungGen: 682646K->233K(691200K)] 686763K->4350K(2089472K), 0.0018965 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:22,073 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jdkPerformance(61) - jdk序列化柬批、反序列化10000次耗時(shí)13699

9次YGC
19.258: [GC (Allocation Failure) [PSYoungGen: 687902K->318K(693248K)] 692530K->4946K(2091520K), 0.0006095 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:27,321 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jsonPerformance(78) - json序列化啸澡、反序列化10000次耗時(shí)5245

5次YGC
24.824: [GC (Allocation Failure) [PSYoungGen: 689776K->121K(694784K)] 694539K->4909K(2093056K), 0.0007943 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:32,671 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessian2Performance(95) - hessian2序列化、反序列化10000次耗時(shí)5349

11次YGC
35.886: [GC (Allocation Failure) [PSYoungGen: 694368K->64K(696320K)] 699315K->5011K(2094592K), 0.0073285 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
2019-03-23 15:31:43,688 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessianLitePerformance(112) - hessian-lite序列化氮帐、反序列化10000次耗時(shí)11017


3次YGC
39.634: [GC (Allocation Failure) [PSYoungGen: 694880K->64K(696832K)] 700003K->5203K(2095104K), 0.0007231 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:48,061 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.kryoPerformance(129) - kryo序列化嗅虏、反序列化10000次耗時(shí)4373


3次YGC
43.816: [GC (Allocation Failure) [PSYoungGen: 694945K->193K(696832K)] 700308K->5556K(2095104K), 0.0007227 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:51,626 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.fstPerformance(146) - fst序列化、反序列化10000次耗時(shí)3564

jvm參數(shù):-Xms2g -Xmx2g -XX:+PrintGCTimeStamps -XX:+PrintGCDetails

可以看出fst最快上沐,kryo次之皮服,json、hessian2速度還不錯(cuò)参咙,但是hessian-lite和jdk基本上一樣慢龄广。

最佳實(shí)踐

  • 序列化的類最好實(shí)現(xiàn)Serializable接口,并寫死serialVersionUID
  • 序列化的類可以加減字段蕴侧,但是最好不要改字段類型
  • 如果是開放出去的api择同,最好采用可讀性好、適合web的json净宵,兼容性也好敲才,和語言沒有耦合,就是浪費(fèi)帶寬
  • 如果是內(nèi)部RPC择葡,可以采用fst和kryo紧武,或者protobuf, thrift。如果要兼容多版本敏储,fst就不太適合
  • 如果有持久化需求阻星,需要考慮到兼容性,可以采用kryo, json

序列化工具類:

static MzKryoPool<Kryo> kryoPool = new MzKryoPool<Kryo>(100);
    static FSTConfiguration fst = FSTConfiguration.createDefaultConfiguration();

    public static <T> byte[] serializeWithJdk(T object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            byte[] bytes = byteArrayOutputStream.toByteArray();
            objectOutputStream.close();
            return bytes;
        } catch (IOException e) {
            throw new OperationException("serialize with jdk fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithJdk(byte[] bytes) {
        try {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Object object = objectInputStream.readObject();
            objectInputStream.close();
            return object;
        } catch (ClassNotFoundException | IOException e) {
            throw new OperationException("deserialize with jdk fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithJson(Object object) {
        return JSON.toJSONBytes(object);
    }

    public static <T> T deserializeWithJson(byte[] bytes, Class<T> cls) {
        return JSON.parseObject(bytes, cls);
    }

    public static byte[] serializeWithHessian2(Object object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream);
            hessianOutput.startMessage();
            hessianOutput.writeObject(object);
            hessianOutput.completeMessage();
            hessianOutput.close();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new OperationException("serialize with hessian2 fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithHessian2(byte[] bytes) {
        try {
            Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(bytes));
            hessian2Input.startMessage();
            Object o = hessian2Input.readObject();
            hessian2Input.completeMessage();
            hessian2Input.close();
            return o;
        } catch (IOException e) {
            throw new OperationException("deserialize with hessian2 fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithHessianLite(Object object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            com.alibaba.com.caucho.hessian.io.Hessian2Output hessian2Output = new com.alibaba.com.caucho.hessian.io.Hessian2Output(byteArrayOutputStream);
            hessian2Output.startMessage();
            hessian2Output.writeObject(object);
            hessian2Output.completeMessage();
            hessian2Output.close();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new OperationException("serialize with hessian-lite fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithHessianLite(byte[] bytes) {
        try {
            com.alibaba.com.caucho.hessian.io.Hessian2Input hessian2Input = new com.alibaba.com.caucho.hessian.io.Hessian2Input(new ByteArrayInputStream(bytes));
            hessian2Input.startMessage();
            Object o = hessian2Input.readObject();
            hessian2Input.completeMessage();
            hessian2Input.close();
            return o;
        } catch (IOException e) {
            throw new OperationException("deserialize with hessian-lite fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithKryo(Object obj) {
        Kryo kryo = kryoPool.obtain();
        //initial 4k, max 10M
        try (Output output = new Output(4096, 10 * 1024 * 1024);) {
            kryo.writeClassAndObject(output, obj);
            return output.toBytes();
        } catch (Exception e) {
            throw new OperationException("deserialize with kryo fail: " + e.getMessage(), e);
        } finally {
            kryoPool.free(kryo);
        }
    }

    public static Object deserializeWithKryo(byte[] bytes) {
        Kryo kryo = kryoPool.obtain();
        try (Input input = new Input(bytes)) {
            return kryo.readClassAndObject(input);
        } catch (Exception e) {
            throw new OperationException("deserialize with kryo fail: " + e.getMessage(), e);
        } finally {
            kryoPool.free(kryo);
        }
    }

    public static byte[] serializeWithFst(Object obj) {
        return fst.asByteArray(obj);
    }

    public static Object deserializeWithFst(byte[] bytes) {
        return fst.asObject(bytes);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末已添,一起剝皮案震驚了整個(gè)濱河市迫横,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酝碳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨狈,死亡現(xiàn)場離奇詭異疏哗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)禾怠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門返奉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝搁,“玉大人,你說我怎么就攤上這事芽偏±啄妫” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵污尉,是天一觀的道長膀哲。 經(jīng)常有香客問我,道長被碗,這世上最難降的妖魔是什么某宪? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锐朴,結(jié)果婚禮上兴喂,老公的妹妹穿的比我還像新娘。我一直安慰自己焚志,他們只是感情好衣迷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酱酬,像睡著了一般壶谒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岳悟,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天佃迄,我揣著相機(jī)與錄音,去河邊找鬼贵少。 笑死呵俏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滔灶。 我是一名探鬼主播普碎,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼录平!你這毒婦竟也來了麻车?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤斗这,失蹤者是張志新(化名)和其女友劉穎动猬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體表箭,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赁咙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼水。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崔拥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凤覆,到底是詐尸還是另有隱情链瓦,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布盯桦,位于F島的核電站慈俯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏俺附。R本人自食惡果不足惜肥卡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望事镣。 院中可真熱鬧步鉴,春花似錦、人聲如沸璃哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随闪。三九已至阳似,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐伴,已是汗流浹背撮奏。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留当宴,地道東北人畜吊。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像户矢,于是被迫代替她去往敵國和親玲献。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java序列化總結(jié) 序列化就是指將Java對(duì)象轉(zhuǎn)換成一系列的字節(jié)梯浪,反序列化即使將一系列的字節(jié)恢復(fù)成Java對(duì)象捌年。序...
    ObadiObada閱讀 511評(píng)論 0 2
  • Dubbo + Kryo 實(shí)現(xiàn)高速序列化 本節(jié)視頻 【視頻】Dubbo 實(shí)現(xiàn)微服務(wù)架構(gòu)-Dubbo-使用 Kryo...
    擼帝閱讀 4,191評(píng)論 0 10
  • 什么是序列化與反序列化 序列化是指把對(duì)象轉(zhuǎn)換為字節(jié)序列的過程(Encoding an object as a by...
    小X感悟閱讀 884評(píng)論 0 4
  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,868評(píng)論 0 24
  • 想要學(xué)習(xí)事件的產(chǎn)生與響應(yīng)過程首先要了解什么是響應(yīng)者對(duì)象,什么是響應(yīng)者鏈條挂洛。 響應(yīng)者對(duì)象:繼承了UIResponde...
    BWLi420閱讀 378評(píng)論 2 1