当前位置: 首页 > 后端技术 > Java

面试官:说说String.intern()和常量池?不同的JDK版本之间有什么区别?

时间:2023-04-02 00:56:49 Java

0。后台有8个基本类型和一个JAVA语言中的特殊类型String。为了使它们在运行时更快并节省内存,这些类型提供了常量池的概念。常量池类似于JAVA系统级别提供的缓存。八种基本类型的常量池由系统协调,其中String类型的常量池比较特殊。主要有两种使用方式:直接使用双引号声明的String对象会直接存储在常量池中。如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern方法会从字符串常量池中查询当前字符串是否存在,如果不存在则将当前字符串放入常量池中1.常量池1.1什么是常量池?JVM常量池主要分为Class文件常量池、运行时常量池、全局字符串常量池、基本类型包装类对象常量池1.1.0方法区的作用是存放类的结构信息Java类。最后,对象的类型信息存放在方法区,实例数据存放在堆中。类型信息是Java代码中定义的常量、静态变量、类中声明的各种方法、方法字段等;实例数据是在Java中创建的对象实例及其值。该区域内存回收的主要目的是回收常量池和卸载内存数据;一般来说,这个区域的内存回收率要比Java堆低很多。1.1.1类文件常量池类文件是一组以字节为单位的二进制数据流。在Java代码的编译过程中,我们编写的Java文件被编译成.class文件格式的二进制数据,存储在磁盘上。这包括类文件常量池。类文件常量池主要存放两个常量:字面量和符号引用。字面量:字面量在java语言层面接近常量概念文本字符串,也就是我们经常声明的:publicStrings="abc";在“abc”中用final修饰的成员变量,包括静态变量、实例变量和局部变量:publicfinalstaticintf=0x101;,finalinttemp=3;而对于基本类型数据(即使是方法中的局部变量),比如intvalue=1,常量池中只保留了它的字段描述符Int和字段名value,它们的字面量不会存在常量池中。Symbolreference:Symbolreference主要设置编译原理相关的概念类和接口的全限定名,即java/lang/String;这样,原来的“.”类名中的用“/”代替,主要用于运行时解析类的直接引用字段的名称和描述符,该字段是类或接口中声明的变量,包括类的名称和描述符类级变量和实例级变量方法,即参数类型+返回值1.1.2运行时常量池Java文件编译成class文件时,会产生上述class文件常量池。JVM在执行一个类时,必须经过加载、链接(验证、准备、解析)、初始化这几个步骤,运行时常量池是JVM将类加载到内存后,会存储类常量的内容运行时常量池中的pool,即类常量池加载到内存后的版本,是方法区的一部分。在解析阶段,符号引用会被直接引用替代,解析过程会查询字符串常量池,即StringTable,确保运行时常量池引用的字符串与字符串常量中的一致水池。与类常量池相比,运行时常量池的一大特点是它是动态的。Java规范并没有要求常量只能在运行时产生,也就是说运行时常量池的内容并不都来自于类常量池。当你可以通过代码生成常量并将它们放入运行时常量池时,使用最多的特性是String.intern()。1.1.3字符串常量池在JDK6.0及之前的版本中,字符串常量池存放在方法区。JDK7.0之后,字符串常量池被移到了堆中。至于为什么会移到堆上,估计是因为方法区的内存空间太小了。HotSpotVM中实现的字符串池功能是一个StringTable类,它是一个Hash表,默认大小和长度为1009;此StringTable在HotSpotVM的每个实例中只有一个副本,并由所有类共享。字符串常量由一个个字符组成,放在StringTable中。在JDK6.0中,StringTable的长度是固定的,长度为1009。因此,如果StringPool中的String过多,会造成hash冲突,导致链表过长。调用String#intern()时,需要在链表上逐一查找,导致性能大幅下降;在JDK7.0中,可以通过参数指定StringTable的长度。字符串常量池设计思想:字符串的分配和其他对象分配一样,消耗时间和空间成本高。作为最基本的数据类型,会创建大量频繁的字符串,极大地影响程序的性能JVM为了提高性能,减少内存开销,在实例化字符串常量时做了一些优化,开辟了一个字符串常量池对于字符串,类似于在缓存中创建字符串常量时,先检查字符在字符串常量池中是否存在字符串是否存在,返回引用实例。如果不存在,则将字符串实例化并放入池中。实现这个优化的基础是因为字符串是不可变的,可以共享运行时实例而不用担心数据冲突。在创建的全局字符串常量池中有一张表,它始终为池中的每一个唯一的字符串对象维护一个引用,也就是说它们总是引用字符串常量池中的对象,所以,常量池中的这些字符串会不被垃圾收集器回收2.String.intern()和字符串常量池/***返回字符串对象的规范表示。*

*一个字符串池,最初是空的,由*类String私下维护。*

*当调用intern方法时,如果池中已经包含一个*等于此String对象的字符串,由*{@link#equals(Object)}方法确定,那么*返回池中的字符串。否则,将此String对象添加到*池中,并返回对此String对象的引用。*

*它跟随现在对于任意两个字符串st,*s.intern()==t.intern()true*当且仅当s.equals(t)true。*

*所有文字字符串和字符串值常量表达式都是*驻留的。字符串文字在*TheJava?LanguageSpecification的第3.10.5节中定义。**@return一个与该字符串具有相同内容的字符串,但*保证来自唯一字符串池。*/publicnativeStringintern();字符串常量池的位置也随着jdk版本的不同而不同。在jdk6中,常量池的位置在永久代(方法区)。对象存储在卷池中。在jdk7中,常量池的位置在堆中。这个时候,常量池就存放了引用。在jdk8中,永久代(方法区)被元空间所取代。这就导致了一个很常见也很经典的问题,看下面的代码。Java8系列教程推荐看这里:https://www.javastack.cn/cate...@Testpublicvoidtest(){Strings=newString("2");实习生();字符串s2="2";System.out.println(s==s2);字符串s3=新字符串("3")+新字符串("3");s3.实习生();字符串s4="33";System.out.println(s3==s4);}//jdk6//false//false//jdk7//false//true这段代码在jdk6中输出falsefalse,在jdk7中输出falsetrue。我们通过图表逐行解释。JDK1.6Strings=newString("2");创建了两个对象,一个是堆中的StringObject对象,一个是常量池中的“2”对象。实习生();在常量池中查找与s变量内容相同的对象,发现已经存在内容相同的对象“2”,返回对象2的地址。Strings2="2";使用字面值创建,在常量池中寻找内容相同的对象,找到则返回对象“2”的地址。System.out.println(s==s2);从上面的分析,s变量和s2变量地址指向不同的对象,所以returnfalseStrings3=newString("3")+newString("3");创建了两个对象,一个是堆中的StringObject对象,一个是常量池中的“3”对象。中间还有2个anonymousnewString("3"),我们就不讨论了。s3.实习生();在常量池中寻找与s3变量内容相同但没有找到“33”对象的对象,在常量池中创建一个“33”对象,并返回“33”对象的地址。字符串s4="33";使用字面值创建,在常量池中寻找内容相同的对象,找到则返回对象“33”的地址。System.out.println(s3==s4);从上面分析,s3变量和s4变量地址指向不同的对象,所以returnfalseJDK1.7Strings=newString("2");在堆中创建两个Object,一个StringObject对象,在堆中创建一个“2”对象,将“2”对象的引用地址保存在常量池中。实习生();在常量池中查找与s变量内容相同的对象,发现已经存在内容相同的对象“2”,返回对象“2”的引用地址。字符串s2="2";使用字面值创建,在常量池中寻找内容相同的对象,找到则返回对象“2”的引用地址。System.out.println(s==s2);从上面的分析,s变量和s2变量地址指向不同的对象,所以returnfalseStrings3=newString("3")+newString("3");创建两个对象,一个是堆中的StringObject对象,一个是堆中的“3”对象,将“3”对象的引用地址保存在常量池中。中间还有2个anonymousnewString("3"),我们就不讨论了。s3.实习生();在常量池中查找与s3变量内容相同但没有找到“33”对象的对象,在常量池中保存s3对应的StringObject对象的地址,并返回StringObject对象的地址。字符串s4="33";使用字面值创建,在常量池中寻找内容相同的对象,找到则返回其地址,即StringObject对象的引用地址。System.out.println(s3==s4);从上面可以看出,s3变量和s4变量的地址指向同一个对象,所以返回true。3、String.intern()的应用在读取和赋值大量字符串的情况下,使用String.intern()会大大节省内存空间。静态最终整数MAX=1000*10000;staticfinalString[]arr=newString[MAX];publicstaticvoidmain(String[]args)throwsException{Integer[]DB_DATA=newInteger[10];随机random=newRandom(10*10000);for(inti=0;i