1. com.alibaba.fastjson存在內(nèi)存泄漏

        共 2447字,需瀏覽 5分鐘

         ·

        2022-01-01 05:23

        [背景]



        發(fā)現(xiàn)線上機(jī)器的元空間在增長(zhǎng),?發(fā)生了FGC.?

        由于拿不到線上機(jī)器的dump文件,?于是乎, 在預(yù)發(fā)環(huán)境,?執(zhí)行jmap命令, 得到dump文件.


        使用MemoryAnalyzer分析dump文件.



        如上圖, 在查看線程信息的時(shí)候,?發(fā)現(xiàn)Dubbo線程,?MQ線程,?xxl-job線程這些線程, 它們`持有`上百KB的內(nèi)存.?常規(guī)情況,?線程不會(huì)`持有`這么大的內(nèi)存.


        拿其中一個(gè)Dubbo線程, 查看下它內(nèi)部的屬性




        如上圖, 在線程的ThreadLocalMap中存在197.05KB的數(shù)據(jù)


        查看ThreadLocalMap中的信息



        如上圖, 在ThreadLocalMap的12號(hào)位置,?存儲(chǔ)了128.02KB的字符數(shù)組.?里面存儲(chǔ)的都是業(yè)務(wù)信息.?


        那么是由哪個(gè)ThreadLocal放到這個(gè)線程的ThreadLocalMap中的呢?


        往下看



        如上圖,在ThreadLocalMap中, ThreadLocal作為Key,?于是右擊圖中的ThreadLocal,?選擇`with incoming references`, 就可以查到到哪些引用了這個(gè)ThreadLocal.



        如上圖,?發(fā)現(xiàn)com.alibaba.fastjson.JSON引用了ThreadLocal.


        根據(jù)這個(gè)線索, 查看了下業(yè)務(wù)代碼.




        在業(yè)務(wù)代碼中,?使用了

        com.alibaba.fastjson.JSON#parseObject()

        跟進(jìn)這個(gè)方法



        有一個(gè)allocateChars方法

        fastjson先從當(dāng)前線程中得到char[],如果沒(méi)有則創(chuàng)建一個(gè)char[], 并放入到線程的ThreadLocalMap中.

        這也是fastjson為了提高性能的一個(gè)手段. 但是它卻造成了內(nèi)存泄漏.?因?yàn)闆](méi)有任何地方調(diào)用了remove()方法.


        排查到這里后, 我去GitHub上查看了下, 原來(lái)在今年(2021年)5月份已經(jīng)有人在GitHub上提出了這個(gè)問(wèn)題.



        地址:?https://github.com/alibaba/fastjson/issues/3751


        我也在下方貼出了我的案例(也就是本文所說(shuō)的)




        但是, 似乎這個(gè)問(wèn)題官方還沒(méi)有給出一個(gè)比較好的解決方案.?(master代碼和最新的1.2.79版本均沒(méi)有看到解決它的`身影`)



        目前有2個(gè)解決方案.

        第一個(gè)方案

        Field charsLocal = JSON.class.getDeclaredField("charsLocal");charsLocal.setAccessible(true);
        if (charsLocal.get(null) instanceof ThreadLocal) { ThreadLocal threadLocal = (ThreadLocal) charsLocal.get(null); threadLocal.remove();}

        通過(guò)反射的方式,?拿到charsLocal屬性, 主動(dòng)調(diào)用它的remove()方法.

        但這種方案并不是最好的方案.?為了提高性能, 不得不把一些事先創(chuàng)建好的char[] 放入到線程的ThreadLocalMap中,?但是如果放入的太多又會(huì)造成內(nèi)存泄漏太多.?? 既不能避免內(nèi)存泄漏, 又不能泄漏太多,?就是下面的第二個(gè)方案.


        第二個(gè)方案

        設(shè)定char[]數(shù)組的最大長(zhǎng)度=128, 假如程序使用了超過(guò)128大小的內(nèi)存,?那么會(huì)自動(dòng)將char[]長(zhǎng)度降到128大小, 保證char[]數(shù)組的長(zhǎng)度不會(huì)超過(guò)128,?做到可控.

        Log4j作為一個(gè)日志框架, 在它的低版本中, 也存在大量?jī)?nèi)存泄漏,?也是因?yàn)門hreadLoal的原因.?作為日志框架,必然要使用ThreadLocal來(lái)提高性能. 但是在Log4j的高版本中, 針對(duì)大量?jī)?nèi)存泄漏的情況, 做了優(yōu)化, 超過(guò)最大值,就進(jìn)行縮容.?也就是按照我們這里說(shuō)的第二個(gè)方案.?部分源碼如下


        //源碼類?org.apache.logging.log4j.message.ParameterizedMessagepublic String getFormattedMessage() {    if (this.formattedMessage == null) {        StringBuilder buffer = getThreadLocalStringBuilder();        this.formatTo(buffer);        this.formattedMessage = buffer.toString();        // 進(jìn)行縮容        StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE);????}    return this.formattedMessage;}
        public static void trimToMaxSize(StringBuilder stringBuilder, int maxSize) {????// 超過(guò)設(shè)定的默認(rèn)最大值, 就進(jìn)行縮容 if (stringBuilder != null && stringBuilder.capacity() > maxSize) { stringBuilder.setLength(maxSize); stringBuilder.trimToSize(); }
        }


        個(gè)人猜測(cè),?fastjson大概率也會(huì)采取第二個(gè)方案, 或者它不理睬這個(gè)內(nèi)存泄漏,?也不好說(shuō).


        祝大家2022新年快樂(lè)!



        瀏覽 69
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 公妇交乱dvd | 黄色一级视频免费看 | 曹逼视频| 日日射天天射 | 免费看A片18 的视频 |