jvm 整体图
常量池
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息 – 简单介绍和理解
运行时常量池(Runtime Constant Pool)
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生 成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
Java虚拟机对于Class文件每一部分(自然也包括常量池)的格式都有严格规定,如每一个字节用 于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、加载和执行,但对于运行时常量池, 《Java虚拟机规范》并没有做任何细节的要求,不同提供商实现的虚拟机可以按照自己的需要来实现 这个内存区域,不过一般来说,除了保存Class文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中[1]。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量 一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常 量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是St ring类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存 时会抛出OutOfMemoryError异常。
案例介绍
`
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1+s2; // == new StringBuilder().append("a").append("b").toString(); new String("ab");
String s5 = "a" +"b"; // javac 在编译阶段优化,结果已经在编译器确定为 ab 这种动态
`
注释:
- 关于编译器优化 在Java中,编译器会对字符串常量进行编译时优化,这个过程通常被称为编译时常量折叠(Compile-Time Constant Folding)或者字符串常量池的优化。这样做的好处是避免了在运行时将两个字符串进行连接操作的开销,提高了代码的执行效率。这种优化只适用于编译时已知的字符串常量,对于运行时拼接的字符串或者变量拼接的字符串则不会进行优化。
- 对s4 这种 属于动态拼接产生的字符串 只会存在于堆中 不回存在于 常量池中的 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
字符串常量池 stringTable
stringTable 特性
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是StringBuilder(l.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
为什么要将字符串常量池移动到堆中
在 Java 7 及之前的版本中,字符串常量池是在永久代(Permanent Generation)中,而不是堆中。然而,在 Java 8 中,为了解决永久代内存溢出的问题,并为 Java 虚拟机提供更好的内存管理,字符串常量池被移动到了堆中的一个特殊区域,称为元空间(Metaspace)。
这个移动的原因主要有以下几点:
- 避免永久代内存溢出:在 Java 7 及之前的版本中,字符串常量池位于永久代中。然而,永久代的大小是有限的,并且经常会因为加载大量类和字符串而导致内存溢出。将字符串常量池移动到堆中的元空间,可以避免永久代内存溢出的问题。
- 更好的内存管理:元空间采用的是本地内存(native memory),而不是 JVM 的堆内存。本地内存的分配和释放由操作系统负责,因此在内存管理方面更加灵活和高效。这也意味着可以更好地利用系统资源,并且更容易调整元空间的大小。
- 简化内存结构:将字符串常量池移到堆中的元空间,简化了 Java 虚拟机的内存结构。永久代作为 Java 堆的一部分,需要进行独立的内存分配和垃圾回收,而元空间的内存管理由操作系统负责,减少了虚拟机内存管理的复杂性。
- 更好的 GC 控制:将字符串常量池放在堆中的元空间,使得垃圾收集器能够更灵活地管理字符串常量池中的内存。垃圾收集器可以根据实际情况动态调整元空间的大小,以更好地满足应用程序的需求,而不需要考虑永久代的固定大小。
stringTable 垃圾回收
stringTable 存在堆内存中 所以 也就意味着和堆中的数据一样 会被gc 回收
在Java中,String
对象一般是不可变的,它们存储在字符串常量池中。这些对象一旦创建,通常会持续存在于应用程序的整个生命周期中,除非满足以下条件之一,否则它们不会被回收:
- 不再被引用: 如果一个
String
对象没有任何引用指向它,即没有任何变量引用它,那么它就成为不可达对象,可能会被Java垃圾回收器回收。 - 字符串常量池重用: 如果一个字符串对象是通过字面值创建的,而且字符串常量池中已经存在相同值的字符串对象,那么会直接重用已存在的对象,而不是创建新的对象。
- 调用intern()方法: 调用
String
对象的intern()
方法会尝试将该对象添加到字符串常量池中,并返回常量池中对应的引用。如果字符串常量池中已经存在相同值的字符串对象,intern()
方法会返回常量池中的对象的引用。 - 字符串对象重新赋值: 如果一个
String
对象被重新赋值为一个新的字符串对象,而原来的字符串对象没有任何其他引用指向它,那么原来的字符串对象可能会成为不可达对象,最终被回收。