V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
badboy17
V2EX  ›  Java

Java 求解答:为什么 JackSon 和 fastJson 里使用 string.intern,欢迎美团技术团队解答:-)

  •  
  •   badboy17 · 2022-08-08 21:16:31 +08:00 · 3199 次点击
    这是一个创建于 598 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题:使用 string.intern 可以节省时间吗,可以节省空间吗?

    前段时间,迁移容器化之后发现机器的 youngGc 时间缓慢上涨,从几十毫秒涨到了几百毫秒,然后出发 full gc 之后,youngGc 时间又重新回落到几十毫秒 后来确定了是系统产生了很多的不同字符串的 key ,同时 jackSon 的 internCache 调用了 string.intern()方法,导致了字符串常量池 stringTable 膨胀,最终导致 YoungGc 扫描变慢

    看了下美团技术团队里写道,fastJson 里使用了 string.intern,可以大大减少时间和空间,不太理解为什么可以节省时间,节省空间我能够理解,但是使用 string.intern ,不是更加的耗时吗?

    https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

    22 条回复    2022-08-09 17:03:49 +08:00
    urnoob
        1
    urnoob  
       2022-08-08 21:37:57 +08:00 via Android
    你先了解下 intern 。
    你的情况的慢是因为短时间内产生了大量不同的字符串进入了 table 。
    美团说的是字符串 xxxx 在进入 table 后,以后相同字符串都不只产生一个对 table 内 xxxx 的引用,也就是说只有一个真正的字符串。内存节省了,gc 也快了。
    sutra
        2
    sutra  
       2022-08-08 21:40:10 +08:00
    美团那个文章不是有一节“####3,fastjson 不当使用 String#intern”?
    badboy17
        3
    badboy17  
    OP
       2022-08-08 21:43:21 +08:00
    @sutra 我们遇到的就是这样的问题,接口传来了不同 Key 的 json ,反序列化导致了 stringTable 一直缓慢的膨胀
    badboy17
        4
    badboy17  
    OP
       2022-08-08 21:49:01 +08:00
    @urnoob gc 为什么一定会变快呢,入参穿进来的字符串一定是一个新的字符串啊,一定需要被 gc 掉,如果使用 return string.intern()的话,这个入参会被 gc 掉,如果直接返回这个入参字符串的话,就相当于多占用了一份空间,在未来的某个时间可能会被 gc ,但是这两种情况都会有一个相同的垃圾需要被 gc ,也就是入参传进来的那个字符串
    badboy17
        5
    badboy17  
    OP
       2022-08-08 22:04:14 +08:00
    @urnoob 可能是我写的顺序有误,其实只是想说下之前遇到这个问题的背景,背景跟问题无关,我的意识并不是说为什么我用了 intern string 变慢了,但是官方说 string.intern 可以减少时间,我的问题只是想问,为什么 intern.string 作为缓存,减少耗时,变快
    Macolor21
        6
    Macolor21  
       2022-08-08 22:20:19 +08:00
    @1023363777 #4 入参的对象只是一个字符串常量池的引用,intern 加速 GC 的含义也是这个,就算有一样的 String 对象,引用的也是这个常量池的地址。这样避免真实 String 对象(数组)被频繁创建、回收。从而达到缓存的目的,由于都是引用对象,因此 GC 非常快
    Macolor21
        7
    Macolor21  
       2022-08-08 22:24:36 +08:00
    还有 intern 耗时的问题,如果常量池已经有了,那么就会直接返回这个常量池对象的地址,不会执行放入常量池的操作

    这个看代码注释就有,对于你的场景,太多的新的字符串对象被放入常量池,那么空间和时间效率都比较低
    kkkiio
        8
    kkkiio  
       2022-08-08 22:26:20 +08:00
    少分配,cache 命中高,对 object mapping 那种场景比较好,对 key 范围大的不好
    kkkiio
        9
    kkkiio  
       2022-08-08 22:28:51 +08:00
    @kkkiio 我记得 HashMap 比较 Key 时会先比较 ref (地址),也是 intern 的优化场景
    badboy17
        10
    badboy17  
    OP
       2022-08-08 22:29:23 +08:00
    @Macolor21 耗时相比起直接就返回入参的字符串,难道不是一定会更慢吗,但是节省了空间是一定的
    badboy17
        11
    badboy17  
    OP
       2022-08-08 22:35:14 +08:00
    @Macolor21
    这是 JackSon 的 intenCache 的源码,为什么入参的 input 你认为一定是一个字符串常量池的引用呢,如果假设现在的 json 串的 Key 是字符串“name”,我认为执行这个方法的入参的这个字符串 Input 对象,一定是一个新的,在堆区分配空间的字符串对象
    public String intern(String input) {
    String result = (String)this.get(input);
    if (result != null) {
    return result;
    } else {
    if (this.size() >= 180) {
    synchronized(this.lock) {
    if (this.size() >= 180) {
    this.clear();
    }
    }
    }

    result = input.intern();
    this.put(result, result);
    return result;
    }
    }
    zjp
        12
    zjp  
       2022-08-08 22:41:00 +08:00
    Jackson 的 InternCache 默认只存 180 条,确定是这个导致的吗?
    badboy17
        13
    badboy17  
    OP
       2022-08-08 22:42:31 +08:00
    @zjp 不是这个,是 string.intern()方法,导致字符串常量池里的 stringTable 缓存了过多的字符串,增加了 yougGC 的扫描耗时
    zjp
        14
    zjp  
       2022-08-08 22:45:17 +08:00
    #13 发现看不懂这个 Cache 了...都必须要调用 string.intern()
    urnoob
        15
    urnoob  
       2022-08-08 22:53:21 +08:00 via Android
    @badboy17
    参考这段 里面多了个 不 字我修正下
    字符串 xxxx 在进入 table 后,以后相同字符串都只产生一个对 table 内 xxxx 的引用,也就是说只有一个真正的字符串。
    虽然不太严谨,但是扫描和回收 10 个对象肯定比 10 引用慢。更何况这些引用可能绝大部分都是在方法内,在栈上,方法退出就释放了 都不涉及 gc
    Macolor21
        16
    Macolor21  
       2022-08-08 23:23:02 +08:00
    @badboy17 #11 第三行这个 return result; 不是吗? JDK 内部也有类似的实现,不过是 native 方法。这个 input 对象,第二次进来就是常量池的内存地址了,因为语言级别上已经也有这个机制
    cubecube
        17
    cubecube  
       2022-08-09 00:02:30 +08:00
    JDK8 上,String.intern 如果数据多了之后,查找非常慢,有非常大的性能问题。
    L0L
        18
    L0L  
       2022-08-09 08:57:49 +08:00
    从现象上看,突然有两个疑问点:
    1 、YoungGC 变慢是扫描了全部的字符串常量池 ?
    2 、YoungGC 无法回收字符串常量池里的 stringTable 缓存的字符串 ?
    zmal
        19
    zmal  
       2022-08-09 14:01:07 +08:00
    当 stringtable 里的字符串被复用的时候就节省时间了呗,intern() 不就是做这个的嘛。
    zmal
        20
    zmal  
       2022-08-09 14:08:15 +08:00
    对你说的 ygc 变慢是 intern()引起的说法存疑。
    badboy17
        21
    badboy17  
    OP
       2022-08-09 16:59:10 +08:00
    @zmal 这个问题很多人遇到过
    badboy17
        22
    badboy17  
    OP
       2022-08-09 17:03:49 +08:00
    @L0L FullGc 会回收,但是 yougGc 不会,YoungGc 的确需要扫描字符串常量池
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3327 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:45 · PVG 18:45 · LAX 03:45 · JFK 06:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.