【发布时间】:2014-08-08 17:24:37
【问题描述】:
我了解 String pool 和 intern() 方法在 java 中的工作原理。这里是一个简单的介绍。
Java 6 中的 String.intern()
在那些美好的过去,所有的实习字符串都存储在 PermGen 中 – 堆的固定大小部分,主要用于存储加载的类 和字符串池。除了显式实习字符串,PermGen 字符串 pool 还包含您程序中之前使用的所有文字字符串 (这里使用了重要的词——如果一个类或方法从未被 加载/调用,其中定义的任何常量都不会被加载)。
Java 6 中这种字符串池的最大问题是它的位置—— 永久代。 PermGen 有一个固定的大小,不能在 运行。您可以使用 -XX:MaxPermSize=96m 选项进行设置。据我所知 知道,默认 PermGen 大小在 32M 和 96M 之间变化,具体取决于 该平台。你可以增加它的大小,但它的大小仍然是 固定的。这种限制需要非常小心地使用 String.intern – 你最好不要使用这种方法来实习任何不受控制的用户输入。 这就是为什么在 Java 6 时代的字符串池主要是在 手动管理的地图。
Java 7 中的 String.intern()
Oracle 工程师对字符串进行了极其重要的更改 Java 7 中的池化逻辑——字符串池被重新定位到堆中。 这意味着您不再受限于单独的固定尺寸 内存区。现在所有字符串都位于堆中,就像其他大多数字符串一样 普通对象,它允许您只管理堆大小,同时 调整您的应用程序。从技术上讲,仅此一项就足够了 重新考虑在 Java 7 程序中使用 String.intern() 的原因。 但还有其他原因。
好的,到目前为止,我对字符串在内存中的存储方式感到满意,直到我遇到了这个工具 Java Visualizer。我编写了以下简单的 Java 类来可视化程序中的内存分配方式。
public class A {
String iWillAlwaysBeOnHeap="I am on heap...!!!";
class Dummy{
private int dummyNumber;
Dummy(int dummyNumber)
{
this.dummyNumber=dummyNumber;
}
}
public static void main(String[] args) {
A a=new A();
a.lonelyMethod();
}
public void lonelyMethod()
{
String lostString="dangling";
String test=new String("dangling");
test.intern();
String duplicateLiteral="dangling";
Dummy dummy=new Dummy(4);
}
}
我得到了以下结果:
如您所见,字符串字面量和具有相同值的对象会重复并存储在堆栈中,而对于方法本地字符串而言,堆空间不会出现在图片中。 起初我很困惑,但后来我搜索并发现了 escape analysis 这是由 JDK 7 自动完成的。但是在我的代码中,我创建了一个 String 对象,它应该存储在堆上,但它在堆栈上,正如您在可视化器输出中看到的那样,但我的 Dummy 类对象存储在堆上。我无法真正掌握这种行为。 方法本地字符串与其他对象和实例级字符串的处理方式有何不同?
【问题讨论】:
-
“在我的代码中,我创建了一个存储在堆栈上的 String 对象”——不,你没有。重复您自己引用的文档:“所有字符串现在都位于堆中”。 (这是在 Java 7 中;在早期版本中,字符串可能在堆中或 PermGen 中,但从不在堆栈中。)
-
@TedHopp 好的,我纠正了自己“我创建了一个应该存储在堆上的字符串对象,但它在堆栈上,正如您在可视化器输出中看到的那样”抱歉我的英语不好..编辑相同..
-
您需要通过
test = test.intern();记住对实习版本的引用。 -
Java 运行时存储对象的方式与 String#intern 和引用的语义无关。例如,VM 可以进行逃逸分析,并且根本不将本地分配/使用的临时对象放入堆中。对于 Java 程序员来说,这种神奇的优化在大多数情况下是显而易见的(基于错误理解的优化通常会抑制这样的事情)。
-
Visualizer 很好,但它不能正确地表示现实(既不是逻辑模型也不是物理模型)。它不将字符串视为对象,也不知道字符串池。
标签: java string string-pool