请问什么是字符串常量池? Java设计者为String提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们需要带着以下三个问题,去理解字符串常量池: - 字符串常量池的设计意图是什么?
- 字符串常量池在哪里?
- 如何操作字符串常量池?
字符串常量池的设计意图是什么?
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:
为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池;
如果字符串已经存在池中,就返回池中的实例引用;
如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享;
实现的基础:
因为字符串是不可变的,可以不用担心数据冲突进行共享;
运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。
我们来看下面一段代码,就是从字符串常量池中获取相应的字符串:
String str1 = “hello”;
String str2 = “hello”;
System.out.printl("str1与str2比较地址" : str1 == str2 ) //true
System.out.printl("str1与str2比较内容" : str1.equals(str2) ) //true
/**说明:str1指向的“hello” 与 str2指向的“hello” 共用一块内存空间*/
字符串常量池在哪里?
在分析字符串常量池的位置时,首先得了解JVM内存模型,JVM内存区域分为线程共享区和线程独占区。
线程共享区包括 [堆] 和 [方法区]
线程独占区包括 [Java虚拟机栈]、[本地方法栈] 和 [陈程序计数器]
方法区:
存放加载的类信息、常量池、静态变量,静态代码块等信息;
类信息包括类的版本、字段、方法、接口等,方法区也被称为永久代。
程序计数器:
是一块比较小的内存区域,是唯一一个不会发生OutOfMemoryError的区域,可以这样理解方法进栈后,每一行代码都有一个标识,程序按着标识往下执行。
Java虚拟机栈:
每个方法执行,都会创建一个栈帧,方法调用进栈,方法结束出栈; 栈帧里面存放着局部变量表,操作数栈,动态链接以及方法出口等;
局部变量表里面存放着基本数据类型,引用类型等; 栈帧伴随着方法的开始而开始,结束而结束;
局部变量表所需的内存空间在编译期间就完成了分配,在运行期间是不会改变的;
栈很容易出现StackOverFlowError,栈内存溢出错误,常见于递归调用;
本地方法栈和Java虚拟机栈:
其实是差不多的,但是也是有区别的Java虚拟机栈为Java方法服务,本地方法栈为native方法服务
堆:
功能单一,就是存储对象的实例,堆其实又分新生代和老年代;
新生代又分Eden、Survivor01和Survivor02三个区域,垃圾收集器主要管理的区域,Eden区回收效率很高。
并不是所有的对象实例都会分配到堆上去,Java虚拟机栈也会分配。堆很容易出现OutOfMemoryError错误,内存溢出
如何操作字符串常量池?JVM实例化字符串常量池时 String s1 = "Hello World"; String s2 = "Hello World"; String s3 = new String("Hello World"); String s4 = s2.intern(); System.out.println("s1 == s2? " + (s1 == s2)); // true System.out.println("s1 == s3? " + (s1 == s3)); // false System.out.println("s1 == s4? " + (s1 == s4)); // true String.intern()判断这个常量是否存在于常量池。 如果存在{ 判断存在内容是引用还是常量{ 如果是引用{ 返回引用地址指向堆空间对象 } 如果是常量{ 直接返回常量池常量 } } } 如果不存在{ 将当前对象引用复制到常量池,并且返回的是当前对象的引用 } 通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。java.lang.String.intern()返回一个保留池字符串,就是一个在全局字符串池中有了一个入口。如果以前没有在全局字符串池中,那么它就会被添加到里面。
|