【问题标题】:Real Life, Practical Example of Using String.intern() in Java?现实生活中,在 Java 中使用 String.intern() 的实际示例?
【发布时间】:2011-03-31 10:32:55
【问题描述】:

我见过许多描述 String intern()'ing 如何工作的原始示例,但我还没有看到可以从中受益的真实用例。

我能想到的唯一情况是拥有一个接收大量请求的 Web 服务,由于严格的模式,每个请求在性质上都非常相似。在这种情况下,通过对请求字段名称进行 intern() 处理,可以显着减少内存消耗。

谁能提供一个在生产环境中使用 intern() 并取得巨大成功的例子?也许是流行的开源产品中的一个例子?

编辑:我指的是手动实习,而不是字符串文字等的保证实习。

【问题讨论】:

    标签: java string permgen string-interning


    【解决方案1】:

    如果您的 N 字符串只能采用 K 不同的值,其中 N 远远超过 K,实习会非常有益。现在,您将最多只存储K,而不是将N 字符串存储在内存中。

    例如,您可能有一个由 5 位数字组成的 ID 类型。因此,只能有10^5 不同的值。假设您现在正在解析一个大型文档,其中包含许多对ID 值的引用/交叉引用。假设该文档总共有 10^9 引用(显然某些引用在文档的其他部分重复)。

    在这种情况下,N = 10^9K = 10^5。如果您没有对字符串进行实习,您将在内存中存储10^9 字符串,其中很多字符串是equals(由Pigeonhole Principle 提供)。如果您在解析文档时得到intern() ID 字符串,并且您不保留对从文档中读取的非实习字符串的任何引用(因此它们可以被垃圾收集),那么您将永远不会需要在内存中存储多个10^5 字符串。

    【讨论】:

    • 我相信这是一个近乎完美的评估,感谢您将其从多基因润滑剂中提取出来。我想出一个具体的例子的困难在于,即使在上述情况下,您通常也可以流式传输输入数据并以块的形式对它进行处理,而不是一次全部处理。假设在远程源的情况下网络延迟/影响可以忽略不计,流式传输与 intern()'ing(如果适用)几乎总是更可取的。问题是,我从未见过满足考虑 intern() 所需的字符串阈值但不能流式传输和分而治之的用例。
    • @Tom: 另见相关的stackoverflow.com/questions/1356341/… - 这也与解析器相关,并受相同的鸽洞原理推动。一个 XML 文档可能有一百万个 <item> 元素,但可能只有很少的元素类型。您可以对元素名称进行实习,以便"item" 仅在内存中出现一次(不包括立即释放的临时垃圾实例,优先于其intern() 代表)。
    • 补充一点很重要,从 Java 7 开始,实习字符串不再存在于 permgen 空间中,因此它们会像任何其他对象一样被垃圾收集。 (来源:oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
    【解决方案2】:

    我们有一个生产系统,可以一次处理数百万条数据,其中许多具有字符串字段。我们应该一直在实习字符串,但有一个错误意味着我们没有。通过修复该错误,我们避免了进行非常昂贵(至少 6 位数,可能是 7 位数)的服务器升级。

    【讨论】:

    • 你能说得更具体点吗?例如什么样的数据?是用户驱动还是内部/cron 驱动?对数据做了什么?等等。有了这个级别的细节,这个例子会更清楚一些。谢谢!
    • 我受限于我能透露的信息,但本质上它是金融交易处理。我们从海量数据库中读取全部数据负载,并对其进行大规模的数据仓库类型操作以识别聚合方面。数据中的一些文本字段在从数据库读取时没有被保留,导致大量内存膨胀和我们的处理能力大大降低。
    【解决方案3】:

    实习有益的示例涉及大量字符串,其中:

    • 字符串可能会在多个 GC 周期中存活,并且
    • 大部分字符串可能有多个副本。

    典型示例包括将文本拆分/解析为符号(单词、标识符、URI),然后将这些符号附加到长期存在的数据结构中。 XML 处理、编程语言编译和 RDF / OWL 三重存储作为实习可能有益的应用程序一时浮现在脑海中。

    但是实习也不是没有问题,尤其是如果上面的假设是不正确的:

    • 用于保存内部字符串的池数据结构占用额外空间,
    • 实习需要时间,而且
    • 一开始,实习并不会阻止重复字符串的创建。

    最后,实习可能通过增加需要跟踪和复制的对象数量以及增加需要处理的弱引用的数量来增加 GC 开销。这种开销的增加必须与有效实习导致的 GC 开销的减少相平衡。

    【讨论】:

      【解决方案4】:

      不是一个完整的答案,但值得深思 (found here):

      因此,在这种情况下,主要的好处是使用== 运算符处理内部化字符串比使用equals() 方法[用于非内部化字符串] 快得多。因此,如果您要比较字符串超过一到三次,请使用 intern() 方法。

      【讨论】:

      • 这是对的,但这种概括有很多例外: - 如果您的字符串长度相同的可能性非常小,并且您可能是 intern() 的字符串数量ing 很高,有人可能会争辩说,由于 equals() 首先进行了大小检查,因此您不必要地将自己暴露在 PermGen OOM 异常中。
      • 你是对的,但是在性能方面你有 O(n) 的 equals 和 O(1) 的 ==。我同意,最坏的情况只有在两个字符串大小相等并且仅在最后一个字符上不同时才会发生。这通常是非常罕见的情况。
      • 答案不正确。 String.equals 做的第一件事是在检查语义相等之前检查引用的相等性。因此,对于两个内部化字符串 == 和 .equals 是,好吧,相等....
      • @Visage - 嘿,不要对我投反对票,对来自 jGuru 的人投反对票;)但你是对的,复制的文本不正确。我会将引文编辑成我认为作者想说的话。
      • @Visage - 调用 string.equals() 实际上做的第一件事是检查空指针(甚至在调用 String.equals() 之前)。 == 因此即使字符串相同也更快。如果您愿意,可以对其进行微基准测试(刚刚尝试过,我在紧密循环中获得了大约两倍的 == 性能)
      【解决方案5】:

      永远,永远,对用户提供的数据使用实习生,因为这可能导致拒绝服务攻击(因为实习生()化的字符串永远不会被释放)。您可以对用户提供的字符串进行验证,但是您又完成了 intern() 所需的大部分工作。

      【讨论】:

      • 您关于未释放 intern()'ed Strings 的观点是不正确的(取决于 JVM)。大多数相关的 JVM 使用弱引用来确保 gc。
      猜你喜欢
      • 1970-01-01
      • 2014-06-04
      • 2011-12-13
      • 1970-01-01
      • 1970-01-01
      • 2018-04-18
      • 2011-02-21
      • 2020-10-15
      • 2012-01-27
      相关资源
      最近更新 更多