【问题标题】:Memory space required for a Static String constant in Java?Java中静态字符串常量所需的内存空间?
【发布时间】:2011-05-02 06:05:38
【问题描述】:

JVM 将分配多少内存 1) 静态字符串 2) 静态整数

我正在探索这个问题,因为我遇到了 Heap Memory Out of Memory 异常, 我的应用程序中有 8 个常量文件,每个文件都有近 300 个静态常量。

将所有常量声明为静态或我可以遵循的任何其他做法是一种好习惯吗?

【问题讨论】:

  • 你能显示一个堆栈跟踪吗?

标签: java performance memory-leaks heap-memory


【解决方案1】:

JVM 会分配多少内存 1) 静态字符串 2) 静态整数

(为简化起见,我假设我们谈论的是 32 位 JVM。还要注意这些数字是近似的,并且是 JVM 特定的。)

简单的第一个 - 每个静态 Integer 变量占用 4 字节内存用于引用加上 4 字节 + 1 对象头(通常是 8 字节 IIRC)。总计 - 16 个字节。

(如果您说的是静态 int 变量,则每个 int 总共有 4 个字节。)

静态的String 变量有点复杂......而且更昂贵:

  • 静态引用变量为 4 个字节。
  • String 对象具有 4 x 4 字节字段 + 1 x 2 字对象标头:即 24 字节。
  • 其中一个字段是指一个 char 数组,它有一个 3 字的标头,内容需要 (string.length() + 3) / 4 字节 - 即 12 + 4 字节的某个倍数。
  • 如果字符串值是编译时常量,则字符串将被保留,这将为字符串池哈希表条目增加一些额外的字节开销。 (32 字节 这是一个合理的猜测。)

将所有这些加起来,你会得到(比如说)每个字符串 80+ 字节,具体取决于字符串长度。但几乎所有这些字节都是(内部)字符串本身的表示。由于使用了static,只有 4 个字节。

我正在探索这个问题,因为我遇到了堆内存不足的异常,我的应用程序中有 8 个常量文件,每个文件都有近 300 个静态常量。

这无关紧要。 OOME 几乎可以肯定是由于其他原因。

将所有常量声明为静态或我可以遵循的任何其他做法是一种好习惯吗?

在一定程度上将实常数声明为静态是一种很好的做法。

但是,源代码中的大量常量变得笨拙,并且由于字节码文件格式的限制,您最终会遇到编译错误。到那时(可能在此之前),您应该将常量从源代码中移出并移到数据库或配置文件中。

您很可能会在内存使用成为重大问题之前就遇到字节码格式强加的限制。

【讨论】:

  • 非常感谢您提供的这条消息灵通的评论。
  • “您很可能在内存使用成为重大问题之前就遇到字节码格式强加的限制”是什么意思? Android JVM 非常慢并且容易进行垃圾收集(每个应用程序只分配了几十 MB)。在应用程序启动时解析一堆 XML 文件非常耗时,所以我正在考虑一个可以解析 XML 并生成 Java 类的工具。这些生成的类将包含很多字符串。我会遇到你说的那个神秘的极限吗?
【解决方案2】:

2400 字符串常量不会耗尽内存。每个慷慨的 10K,那将只有 24MB。在更常见的每个 100 字节的速率下,它只有 240K。

我会在别处寻找内存猪。

【讨论】:

  • 你能告诉我如何检查吗?以及分析它的任何工具?
  • Harigm,您可以使用任何分析器来查看是什么在吞噬您的记忆。 VisualVM (visualvm.dev.java.net) 是一个免费的分析器,可用于分析应用程序的内存消耗。
【解决方案3】:

字符串对象占用多少空间当然取决于字符串的长度。

除了字符串本身的字符之外,一个字符串包含一些控制字段,但我将它们计为 28 个字节加上另一个嵌入对象(ObjectStreamField),我不确定它有多大,但无论如何它可能是几个十几个字节。每个字符占用 2 个字节。我认为,您需要 8 个字节的句柄。如果您是字符串,那么每个字符串可能有 20 个字符左右?也许每个字符串有多达 100 个字节。所以就像 Thilo 说的,2400 个字符串可能听起来很多,但它会占用数百 kB。除非您谈论的是嵌入在手表中的 Java 或某种高度受限的环境,否则很难想象这会成为影响您记忆的重要因素。

【讨论】:

    【解决方案4】:

    我假设它们都是静态的最终的,因为您将它们称为常量。

    如果原始类型或字符串被定义为常量并且值在编译时已知,则编译器会用其值替换代码中各处的常量名称。这称为compile-time constant。对于原始类型,不需要使用堆内存。字符串是interned,堆里只需要存一份。

    无论如何,我不相信 8 x 300 字符串会导致内存不足。您的问题一定出在其他地方。

    【讨论】:

    • 我知道编译器会为原始类型执行此操作,但即使是字符串?并且至少一个 String 实例必须在堆内存中,即使其他人共享它。
    • 我认为常量字符串是interned,但占用堆空间。此外,虽然编译时常量可能不使用任何堆空间,但它们肯定会占用 RAM。
    • @Thilo, Burleigh Bear:你是对的,实习版本存储在堆内存中。我会更新的。
    【解决方案5】:

    你的常量字符串有多大?

    字符串使用的内存大约为 8 * 300 * 2 * 平均长度 = ~5k * 平均长度。使用所有内存似乎还不够。

    将常量声明为 static 和 final 确实是一个好习惯。例如

    public static final String A_STRING = "this String is constant"; 
    

    注意,'final' 修饰符使引用变为常量,而不是对象本身。

    【讨论】:

    • 只是对上述内容的补充:它是由 final 修饰符保持不变的引用,但是当编译器看到对字符串文字的最终引用时,它会将其识别为 compile-时间常数和实习生字符串。这是由 JLS 指定的:java.sun.com/docs/books/jls/third_edition/html/…
    • @Grodriguez - 实习发生是因为字符串 value 是编译时常量,而不是因为变量被声明为 final
    • @Stephen C:是的,你是对的。这就是我的意思(编译器将字符串文字识别为编译时常量),尽管我评论中的措辞可能具有误导性。感谢您的澄清。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    相关资源
    最近更新 更多