Java8,重复字符串浪费内存

Java8,重复字符串浪费内存,java,string,memory-leaks,Java,String,Memory Leaks,我正在调查运行在Java8JVM上的Grails3.3.10服务器中的内存泄漏。我从一台内存不足的生产服务器上获取了一个堆转储,并使用进行了分析。html报告说一些内存浪费在重复字符串上,开销为19.6%。其中大部分都浪费在空字符串“”的副本上,并且大部分来自数据库读取。关于这一点,我有两个问题 我应该开始实习字符串,还是手术成本太高而不值得 我的代码中有相当一部分涉及elasticsearch中的深度嵌套JSON结构,我不喜欢代码的脆弱性,因此我创建了一个小的帮助器类,以避免从JSON访问数

我正在调查运行在Java8JVM上的Grails3.3.10服务器中的内存泄漏。我从一台内存不足的生产服务器上获取了一个堆转储,并使用进行了分析。html报告说一些内存浪费在重复字符串上,开销为19.6%。其中大部分都浪费在空字符串“”的副本上,并且大部分来自数据库读取。关于这一点,我有两个问题

  • 我应该开始实习字符串,还是手术成本太高而不值得

  • 我的代码中有相当一部分涉及elasticsearch中的深度嵌套JSON结构,我不喜欢代码的脆弱性,因此我创建了一个小的帮助器类,以避免从JSON访问数据时出现拼写错误

  • 这有助于我避免像这样的打字错误:

        Integer userId = json.get("userid"); // Notice the lower case i. This returns null and fails silently
        Integer userId = json.get(S.userId); // If I make a typo here the compiler will tell me.
    
    我对此相当高兴,但现在我在猜测自己。出于某种原因,这是个坏主意吗?我没见过其他人这样做。这不会导致创建任何重复字符串,因为它们只创建一次,然后在我的解析代码中引用,对吗

    问题1:我应该开始实习字符串,还是一次手术的成本太高而不值得

    如果没有关于字符串是如何创建的以及它们的典型生存期的更多信息,很难说,但是一般的答案是否定的。这通常是不值得的

    (实习也不能修复你的内存泄漏。)

    以下是一些原因(恐怕有点手舞足蹈):

    • 插入字符串不会阻止正在插入的字符串被创建。您的代码仍然需要创建它,GC仍然需要收集它

    • 有一个隐藏的数据结构来组织插入的字符串。这需要内存。它还需要CPU来检查一个字符串是否在内部数据结构中,并在需要时添加它

    • GC需要对内部数据结构执行特殊(弱引用)操作,以防止其泄漏。那是一笔开销

    • 固定的绳子比非固定的绳子寿命更长。它更有可能被保留到“旧”堆中,这会导致其寿命延长更长。。。因为“旧”堆被GC’ed的次数较少

    如果您使用的是G1收集器,并且重复字符串通常很长,则可能需要尝试启用G1GC字符串重复数据消除(请参阅)。否则,让GC处理字符串可能会更好。javagc的设计目的是有效地处理大量的对象(比如字符串),这些对象在创建之后很快就会被丢弃

    如果是您的代码在创建Java字符串,那么可能需要对其进行调整,以避免创建新的零长度字符串。根据@ControlAltDel的注释手动插入零长度字符串可能不值得这样做

    最后,如果您打算以某种方式减少重复,我强烈建议您进行设置,以便能够衡量优化的效果:

    • 你真的节省了内存吗
    • 这会影响GC运行的速率吗
    • 这会影响GC暂停吗
    • 它是否会影响请求时间/吞吐量
    如果测量结果表明优化没有起到作用,那么您需要撤销它


    出于某种原因,这是个坏主意吗?这不会导致创建任何重复字符串,因为它们只创建一次,然后在我的解析代码中引用,对吗

    我想不出任何理由不那样做。它当然不会直接导致创建重复字符串


    另一方面,这样做并不能简单地减少字符串重复。表示文字的字符串会自动插入。

    字符串持有类的问题在于,您使用的语言与其语言设计不符

    类应该引入类型。一个不提供任何实用程序的类型,因为它是“用字符串可以说的一切”类型,很少有用。虽然在许多程序中都会出现这种情况,但它们通常会引入比“所有东西都在这里”更多的行为。例如,语言环境数据库为不同的语言提供替换字符串

    我首先要做一些合理的列举。错误消息可能很容易转换为枚举,枚举具有简单的自动转换字符串表示形式。这样你就可以得到你的“打字错误检测”和一个内置的分类

     DiskErrors.DISK_NOT_FOUND
     Prompts.ASK_USER_NAME
     Prompts.ASK_USER_PASSWORD
    
    这种变化的副作用可以达到你想要的目标;但要小心,这些变化往往意味着可读性的丧失

    可读性不是你认为容易阅读的东西,而是从未使用过代码的人认为容易阅读的东西

    如果我发现“您选择的硬盘未找到”有问题,那么我会在代码库中查找字符串“您选择的硬盘未找到”。这会让我在两个地方落脚:

  • 在代码块中,出现了错误消息
  • 在将该字符串映射到名称的表中
  • 在引发相同错误消息的许多代码块中
  • 通过表映射,我可以进行第二次搜索,搜索名称的使用位置。这让我想到了几个场景:

  • 它只在一个地方使用
  • 它在许多地方被使用
  • 在一个地方,出现了一种代码维护问题。您现在有一个常量,该常量不被代码的任何其他部分使用,该常量保存在不靠近使用它的地方。这意味着,要进行任何需要充分了解影响的更改,必须有人记住远程常量的值,以了解逻辑更改是否应与更新的错误消息相结合。并不是错误消息的更新导致了额外的错误,而是从代码b中删除了错误消息
     DiskErrors.DISK_NOT_FOUND
     Prompts.ASK_USER_NAME
     Prompts.ASK_USER_PASSWORD