1.字符串在内存里面到底是怎么样存在的?
以String类型的对象在堆区申请到一块内存区,一共24个字节,其中mark_word占8个字节,_klass指针4个字节,value4个字节,hash4个字节,coder1个字节,对齐3个字节,value指向存放真正字符的byte[]数组对象。当然这个大小跟hotspot是32位还是64位有关,以及64位情况,是否开启指针压缩有关。
-XX:+UseCompressedOops:针对oop指针压缩,-XX:+UseCompressedClassPointers:针对klass指针压缩
以上24个字节是针对64位默认,也就是开启oop指针压缩和klass指针压缩的情况下。
2.String的 intern 方法到底干了什么?
如果常量池中已经有了这个字符串,那么直接返回常量池中它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用。
3.s1.intern(); 和 s1 = s1.intern();一样吗?
不一样。
s1.intern(); 将s1的引用保存一份到字符串常量池。如果常量池中已经有了这个字符串,相对于什么都没做。
s1 = s1.intern(); 将s1的引用保存一份到字符串常量池,如果常量池中已经有了这个字符串,返回常量池中的引用赋值给s1,那么也就是说s1的引用变了,如果常量池中没有这个字符串,那就将它的引用保存一份到字符串常量池。
区别在于:s1=s1.intern(); s1的引用值可能会发生变化,但是s1.intern(); 对s1的引用值不产生任何改变。
4.String s = “hello”;和String s = new String(“hello”);还有String s = “he”+”llo”;以及String s = new String(“he”)+new String(“llo”);的区别是什么?
(1).String s = “hello”;
s指向堆区的一个String对象,并且该对象引用保存在字符串常量池里面。
(2).String s = new String(“hello”);
s指向堆区的一个String对象,并且该对象引用没有保存在字符串常量池里面。执行s=s.intern(); 后,s的引用值改变,指向字符串常量池存在的引用值。
(3).String s = “he”+”llo”;
和String s = “hello”;一样,在编译阶段就会合并为一个字符串。所以跟(1)是一样的。
(4).String s = new String(“he”)+new String(“llo”);
这个要稍微复杂一点,涉及到方法调用,在JDK9以下,采用StringBuilder.append拼接,返回String,在JDK9及以上,采用StringConcatFactory.makeConcatWithConstants拼接,返回String。并且该对象引用没有保存在字符串常量池里面。执行s=s.intern(); 后,s的引用值改变,指向字符串常量池存在的引用值。
5.字符串哪些情况会进入字符串常量池?以及什么时候进入字符串常量池?
只要是””包裹的字符串,都会进入字符串常量池,以及拼接后调用intern方法的字符串。那可能就人会问了,那String s = new String(“hello”);和String s = new String(“he”)+new String(“llo”);这里为什么没有进入字符串常量池,其实,那是因为String s = new String(“hello”);这里产生了两个对象,一个是”hello”包裹的匿名对象,它的引用地址会进入字符串常量池,另外一个是new出来的对象,也就是s最终指向的引用对象,只不过new出来的对象和”hello”包裹的匿名对象指向了相同的byte[]数组对象。而String s = new String(“he”)+new String(“llo”);涉及到5个对象,一个是”he”包裹的匿名对象,它的引用地址会进入字符串常量池,还有一个是”llo”包裹的匿名对象,它的引用地址会进入字符串常量池,另外就是两个new出来的String对象,new出来的两个String对象拼接之后,会再生成1个新对象返回给s,这3个对象都没有进入字符串常量池,如果调用s.intern(),则新生成的拼接String对象会进入字符串常量池。
如果是””包裹的字符串,在类加载的时候,有一个解析阶段,其实在这个时候就进入了字符串常量池,但是对于CONSTANT_String类型在HotSpot VM中的延迟执行的,真正执行是要到首次执行ldc指令的时候,才会真正的进入字符串常量池。而调用String的 intern 方法,方法执行的时候,就会进入字符串常量池。
6.字符串常量池是什么?在内存里面到底是怎么样存在的?和字符串到底是什么关系?
字符串常量池是一个存储java.lang.String实例引用的HashMap,key就是对应对应字符串数组,value就是java.lang.String实例引用。存在方式见String在内存里面的结构图所示。
7.字符串常量池和运行时常量池,常量池是什么关系?它们分别是什么?
常量池/class文件常量池/静态常量池:存在于class字节码文件中的一段二进制数据,存储在磁盘或网络中,未加载到内存。
运行时常量池:就是class文件中的常量池,在运行的过程中,加载到内存后,在方法区的一块内存数据,对应JVM的SymbolTable数据结构存储,存在于元空间。
字符串常量池:字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:为字符串开辟一个字符串常量池,类似于缓存;创建字符串常量时,首先坚持字符串常量池是否存在该字符串;存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中。对应JVM的StringTable数据结构存储,StringTable本体存在元空间,实际引用对象存在于堆区。
整型常量池/包装类型常量:此种常量池并非JVM层面,而是Java层面的封装,比如IntegerCache(Integer的内部类),实现了Integer的常量池,其它还有Byte, Short, Integer, Long, Character,Boolean。
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
ConstantPoolCache主要用于存储某些字节码指令所需的解析(resolve)好的常量项,例如给[get|put]static、[get|put]field、invoke[static|special|virtual|interface|dynamic]等指令对应的常量池项用。
每个InstanceKlass关联着一个ConstantPool,作为该类型的运行时常量池。这个常量池的结构跟Class文件里的常量池是一一对应的。Constant Pool 是一个全局公共数组,每加载一个类,这个类元数据的指针就会加入这个数组,指针指向的实际的数据,例如字面量,符号常量等等,都会放入全局公共 Symbol Table 以及 全局公共 StringTable。