[alibaba/fastjson]charsLocal内存强引用,线程池模式下会导致内存浪费

2024-09-20 435 views
3

https://github.com/alibaba/fastjson/blob/14a95cc5b07e76ab8c0c72192c1e739cb157c7fc/src/main/java/com/alibaba/fastjson/JSON.java#L1284

    private final static ThreadLocal<char[]> charsLocal = new ThreadLocal<char[]>();
    private static char[] allocateChars(int length) {
        char[] chars = charsLocal.get();

        if (chars == null) {
            if (length <= 1024 * 64) {
                chars = new char[1024 * 64];
                charsLocal.set(chars);
            } else {
                chars = new char[length];
            }
        } else if (chars.length < length) {
            chars = new char[length];
        }

        return chars;
    }

直接使用65536个字符是不是太浪费了?大部分场景下都是小对象,但是65536直接内存常驻,利用率非常低,内存水位直接被ThreadLocal拉高,无法GC掉。大对象用不到,小对象还浪费……

回答

7

啊这~ 这是大问题啊,简单搜了下,allocateCharsJSON.parse中被用到,并且完全没有释放的地方。只要100个常驻线程有用过这东西,直接就是6G内存消耗?@wenshao

4

啊这~ 这是大问题啊,简单搜了下,allocateCharsJSON.parse中被用到,并且完全没有释放的地方。只要100个常驻线程有用过这东西,直接就是6G内存消耗?@wenshao

6M,要是6G早就全员原地炸了?。

7

最近几天,线上服务器元空间一直在增长, 于是把预发环境机器的内存dump出来看一下, 无意间发现很多Dubbo线程和MQ线程`把持`了很多内存空间, 上百KB, 不应该呀, 看了业务代码, 看了架构组提供的Jar包是否存在默认拦截器之类的, 但是都没有找到导致问题的根源. 下班后花了2天还是没查到问题, 第3天, 还是回到dump文件, 继续分析它

图片

找了一个MQ线程,看一下它内部属性

图片

继续查看内部的ThreadLocalMap

图片

在它的112槽存储了一个128.05KB字节数组, 里面的信息就是业务打印的日志内容.

继续查看哪些类使用了这个java.lang.ThreadLocal @ 0x701392b28

图片

发现了class com.alibaba.fastjson.JSON @ 0x700f14d78这个对象的charsLocal使用了上面的ThreadLocal

图片

查看源码后, 大吃一惊, 这个ThreadLocal只负责set, 造成很多线程都`把持`很多无用的信息, 浪费了很多内存空间.

6

看上面的回复说, 这个问题已经修复了 . 而经过测试最新的1.2.79版本 以及查看master代码和1.2.79版本代码, fastjson并没有主动`帮助`使用者清除ThreadLocal. 官方是希望使用者自己去手动清除ThreadLocal? @wenshao

附录 个人理解, 在内存空间和执行效率两者的选择上, fastjson依然选择效率优先, 宁愿牺牲一些内存空间, 也不会放弃效率, 因此也不会调整原有代码逻辑, 是吗?

4

看上面的回复说, 这个问题已经修复了 . 而经过测试最新的1.2.79版本 以及查看master代码和1.2.79版本代码, fastjson并没有主动帮助使用者清除ThreadLocal. 官方是希望使用者自己去手动清除ThreadLocal? @wenshao

附录 个人理解, 在内存空间和执行效率两者的选择上, fastjson依然选择效率优先, 宁愿牺牲一些内存空间, 也不会放弃效率, 因此也不会调整原有代码逻辑, 是吗?

Maybe the author has not considered this issue~

6

128k,一个实例最多时几百个线程,其实影响还好。如果调的很小,会造成老年代垃圾。如果完全不存,频繁初始化又影响内存又造成垃圾。估计这就是不改的原因。

7

128k,一个实例最多时几百个线程,其实影响还好。如果调的很小,会造成老年代垃圾。如果完全不存,频繁初始化又影响内存又造成垃圾。估计这就是不改的原因。

主要温少一直没有回复过,其实提供一个配置项,比如说-Dfastjson.charsLocal.length=1024就可以解决问题,空间紧张的调小点,大报文需要性能的调大点

5

128k,一个实例最多时几百个线程,其实影响还好。如果调的很小,会造成老年代垃圾。如果完全不存,频繁初始化又影响内存又造成垃圾。估计这就是不改的原因。

主要温少一直没有回复过,其实提供一个配置项,比如说-Dfastjson.charsLocal.length=1024就可以解决问题,空间紧张的调小点,大报文需要性能的调大点

确实是,可调的话更好,不同业务根据自己特性选择。序列化那个类提供了可调的配置。这个JSON类却没有。