String str = new String("Hello")
思考:上面代码创建几个对象?
琳琳不假思索回答:创建一个对象
我直接回答琳琳说不完全对,不可能是一个,也可能是两个,使用new 关键字创建字符串时,Java虚拟机会在字符串常量池查找有没Hello这个字符串。演示图如下:
- 如果有,就不会在字符串常量池中创建Hello该对象,直接在堆中创建一个Hello字符串,然后将堆中Hello对象地址返回赋值给变量str.如果没有
- 如果常量池有,先在字符串常量池中创建一个'Hello'的字符串对象,然后再在堆中创建一个'Hello'的字符串对象,然后将堆中这个'Hello'的字符串对象地址返回赋值给变量 str。
说明:栈上主要存储两类数据:基本数据类型的变量和对象的引用,而对象本身则存储在堆上
琳琳问我,为什么要先在字符串常量池中创建对象,然后再在堆上创建? 这样不是多此一举
是的,由于字符串使用频率很高,Java虚拟机为了减少内存开销和提高性能,在创建字符串对象时候进行了一些优化,特意给字符串开辟一块空间-----字符串常量池
2. 字符串常量池的作用
琳琳又问,我们平常创建对象采用双引号方式创建字符串对象,而不是通过new 关键字方式创建
String str = "Hello"
思考:采用双引号方式创建字符串对象和new 关键字方式创建区别
String str = "Hello" 时,Java 虚拟机会先在字符串常量池中查找有没有Hello这个字符串对象,
- 如果有,则不创建任何对象,直接将字符串常量池中这个Hello的对象地址返回,赋给变量 str
- 如果没有,在字符串常量池中创建Hello这个对象,然后将其地址返回,赋给变量 str
Java 虚拟机创建了一个字符串对象 "Hello",它被添加到了字符串常量池中,同时引用变量 str 存储在栈上,它指向字符串常量池中的字符串对象 "Hello"。这样就省了一步,比之前高效了。
3. 举例说明
String str = new String("Hello");
String str1 = new String("Hello");
思考:上面例子创建了几个对象
创建三个对象,首先在字符串常量池创建一个,其次堆上创建两个
String str = new String("spring葵花宝典");
String str1 = new String("spring葵花宝典");
思考:双引号创建字符串创建几个对象
创建一个对象,就是字符串常量中的那个对象,这样就提高了性能
4. 字符串常量池在内存中位置
琳琳又问,哥,字符串常量池在内存中的什么位置呢?
我说,你这个问题问得好
分为三个时间段
Java7之前
在Java 7之前,字符串常量池位于永久代(Permanent Generation)中,而普通的字符串对象则存储在Java堆(Java Heap)中。字符串常量池用于存储静态数据,包括字符串常量,而堆用于存储对象实例和数组。
当我们创建一个字符串常量时,它会被储存在永久代的字符串常量池中。如果我们创建一个普通字符串对象,则它将被储存在堆中。如果字符串对象的内容是一个已经存在于字符串常量池中的字符串常量,那么这个对象会指向已经存在的字符串常量,而不是重新创建一个新的字符串对象
Java7
需要注意的是,永久代的大小是有限的,并且很难准确地确定一个应用程序需要多少永久代空间。如果我们在应用程序中使用了大量的类、方法、常量等静态数据,就有可能导致永久代空间不足。这种情况下,JVM 就会抛出 OutOfMemoryError 错误
Java 7 开始,为了解决永久代空间不足的问题,将字符串常量池从永久代中移动到堆中。这个改变也是为了更好地支持动态语言的运行时特性。
Java 8
在Java 8中,永久代(PermGen)被取消,取而代之的是元空间(Metaspace)。元空间是一块本机内存区域,与JVM内存区域分开。它承担了存储类信息、方法信息、常量池信息等静态数据的功能,与永久代的作用相似。
与永久代不同的是,元空间具有一些优点:
- 动态调整大小:元空间的大小可以动态调整,这意味着不会因为元空间的大小限制而导致OutOfMemoryError错误。
- 使用本机内存:元空间使用本机内存而不是JVM堆内存,这有助于避免堆内存的碎片化问题,提高了内存利用率。
- 垃圾收集与堆分离:元空间中的垃圾收集与堆中的垃圾收集是分离的。这意味着在应用程序运行过程中进行类加载和卸载时,不会频繁触发Full GC,从而减少了系统资源的消耗。
总的来说,Java 8中的元空间相较于永久代带来了更好的性能和更可靠的内存管理。