Java中静态字符串常量所需的内存空间是多少?

3

JVM会为以下变量分配多少内存: 1)静态字符串 2)静态整数

我在探究这个问题,因为我的应用程序出现了堆内存溢出异常。我有8个常量文件,每个文件都有近300个静态常量。

将所有常量声明为静态的好处是什么?还有其他可行的做法吗?


1
你能显示一下堆栈跟踪吗? - undefined
5个回答

7
JVM将分配多少内存给1)静态字符串2)静态整数?
(为简化事情,我假设我们谈论的是32位JVM。还要注意这些数字是近似值,并且与JVM有关。)
首先是容易的-每个静态整数变量占用4字节内存作为引用加上4字节+ 1个对象头(通常为8字节IIRC)。总共-16字节。
(如果您正在谈论静态int变量,则每个int为4个字节。)
静态字符串变量有点更复杂...也更昂贵:
静态引用变量为4字节。
String对象具有4 x 4字节字段+ 1 x 2字对象头:即24字节。
其中一个字段引用了具有3个字头的char数组,并且需要(string.length()+3)/ 4字节的内容-即12 +多个4字节。
如果String值是编译时常量,则String将被合并,这将为字符串池哈希表条目添加一些额外的字节开销。 (32字节是一个合理的猜测。)
把它全部加起来,你得到(比方说)每个字符串80+字节,具体取决于字符串长度。但几乎所有这些字节都是(内部)字符串本身的表示。只有4个字节是由于使用静态而产生的。
我正在探索这个问题,因为我得到了堆内存溢出异常,在我的应用程序中有8个常量文件,每个文件都有接近300个静态常量。
那微不足道。 OOME几乎肯定是由其他原因引起的。
将所有常量声明为静态是否是一种好的做法?还可以遵循其他做法吗?
将真正的常量声明为静态变量是一个好的做法,但要有限度。
但是,源代码中大量的常量变得笨重,并且您最终会因字节码文件格式的限制而遇到编译错误。在那一点上(并且可能远远在此之前),您应该将常量从源代码中移出,并将其放入数据库或配置文件中。
在内存使用成为重要问题之前,您很可能会遇到字节码格式所施加的限制。

非常感谢您提供这个信息丰富的评论。 - undefined
你说的“在内存使用成为一个重要问题之前,你很可能会遇到字节码格式所施加的限制”是什么意思?Android虚拟机(JVM)非常慢且容易发生垃圾回收(每个应用程序只分配了几十兆字节)。在应用程序启动时解析一堆XML文件会消耗大量性能,所以我正在考虑一个可以解析XML并生成Java类的工具。这些生成的类将包含大量字符串。我会遇到你提到的那个神秘限制吗? - undefined
@Sebien - 举个例子 - https://dev59.com/h2w15IYBdhLWcg3wfr9y - undefined
对于2023年的读者来说……我认为你会发现最近Android版本的GC(垃圾回收)要好得多,而且Android应用程序可以拥有更大的堆内存……因为现在的手机比10年前拥有更多的RAM。在启动时解析数据文件不应该成为问题。 - undefined

4

2400个字符串常量不会耗尽你的内存。如果每个字符串常量占用10K,那只需要24MB。如果按照更常见的100字节每个计算,那只需要240K。

我会在其他地方寻找内存占用较大的程序。


你能告诉我如何进行检查吗?还有分析这个的工具吗? - undefined
Harigm,你可以使用任何分析器来查看是什么在消耗你的内存。VisualVM(https://visualvm.dev.java.net/)是一个免费的分析器,你可以用它来分析你的应用程序的内存消耗情况。 - undefined

2

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

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


1

我假设这两个都是静态的final,因为你将它们称为常量。

如果一个原始类型或字符串被定义为常量,并且在编译时已知其值,编译器会将代码中的常量名称替换为其值。这被称为编译时常量。对于原始类型,不需要使用堆内存。字符串被放入池中,只需在堆中存储一份副本。

不管怎样,我不相信8 x 300个字符串会引起内存溢出的问题。你的问题肯定出在其他地方。


1
我知道编译器对于原始类型会这样处理,但是对于字符串也是一样吗?而且至少一个字符串实例必须在堆内存中,即使其他所有人都共享它。 - undefined
3
我认为常量字符串被内部化了,但占用堆空间。同时,编译时常量可能不使用任何堆空间,但它们肯定会占用 RAM。 - undefined
@Thilo, Burleigh Bear: 你们说得对,内部版本存储在堆内存中。我会进行更新。 - undefined

0

你的常量字符串有多大?

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

将常量声明为静态和最终是���个很好的做法。例如:

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

请注意,由于使用了'final'修饰符,被声明为常量的是引用,而不是对象本身。

1
只是一个简短的注释来补充上面的内容:通过final修饰符,引用被设为常量。然而,当编译器看到对字符串字面值的final引用时,它会将其识别为编译时常量并将字符串进行内部化。这在JLS中有明确规定:http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#5313 - undefined
1
@Grodriguez - 因为String value是一个编译时常量,而不是因为变量被声明为final,所以会发生字符串的interning。 - undefined
@Stephen C:是的,你说得对。这就是我所指的(编译器将字符串字面值识别为编译时常量),尽管我在评论中的措辞可能有些误导。谢谢你澄清。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接