这是Sun JDK 1.6u21,x64。
我有一个类用于试验perm gen用法,它只包含一个大字符串(512k个字符):
public class Big0 {
public String bigString =
"A string with 2^19 characters, should be 1 MB in size";
}
我使用getUsage().toString()
对象上的MemoryPoolMXBean
检查永久生成的perm gen用法(在u21中称为“PS Perm Gen”,尽管它的名称略有不同,版本不同,或者版本不同垃圾收集者。
当我第一次引用该类时,通过阅读Big0.class
来说,perm gen跳过~500 KB - 这就是我所期望的,因为字符串的常量池编码是UTF-8,而我是仅使用ASCII字符。
然而,当我实际创建这个类的实例时,perm gen跳跃大约2 MB。由于这是1 MB内存中的字符串(每个UTF16字符2个字节,当然没有代理),我很困惑为什么内存使用量是双倍的。
如果我将字符串设为静态,则会产生相同的效果。如果我使用final,则无法编译,因为我超过了65535字节的常量池项的限制(不确定为什么离开最终关闭也避免了 - 考虑一个奖金问题)。
任何洞察力都赞赏!
编辑:我还应该指出,这种情况发生在非静态,最终的非静态和静态字符串中,但不适用于最终的静态字符串。由于这已经是字符串常量的最佳实践,也许这主要是学术兴趣。
答案 0 :(得分:2)
我认为这是你的测试课的人工制品。我创建了一个类似的类,然后用javap反编译它。
[eclipse] java编译器将String文本分成多个块,每个块不超过64k。用于初始化非常量字段的字节码包括将源字符串与一系列StringBuilder操作拼凑在一起。虽然这是最后一个巨大的字符串,但是由它构成的大原子占据了恒定池中的空间。
答案 1 :(得分:0)
Java字符的宽度为每个字符2个字节(无论是ASCII还是高于255的代码点)。我认为你所看到的是Java VM在初始化类时(在实例创建之前完成)将字符串的内部类文件存储(修改的UTF8)版本转换为其内部扩展形式。
答案 2 :(得分:0)
虽然类文件格式specifies modified UTF-8作为String
文字的存储格式,但运行时的内部格式为UTF-16。 String
将其数据以UTF-16编码的形式存储在char[]
中(但通常,它依赖于实现)。大多数字符在此编码中占用2个字节(BMP之外的字符占用更多)。
我见过对包含rt.jar
实现的修改后的java.lang.String
的引用,该实现具有专用于仅ASCII字符串的代码路径/存储,可显着降低内存需求。
编辑:根据this reference,自Java 6 Update 21以来,此选项似乎已进入正常的Oracle JRE:
-XX:-XX:+ UseCompressedStrings
对字符串使用byte [],可以表示为纯ASCII。 (在Java 6 Update 21性能发布中引入)
(通过this answer找到)。
答案 3 :(得分:0)
一个好的内存分析器(我个人使用并且非常喜欢你的Java分析器)应该能够告诉你内存的使用位置。