1. string常量问题
1.1. 运行时常量池与Class文件常量池区别
JVM对Class文件中每一部分的格式都有严格的要求,每一个字节用于存储那种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行;但运行时常量池没有这些限制,除了保存Class文件中描述的符号引用,还会把翻译出来的直接引用也存储在运行时常量区
java代码编译后,程序会先被编译为.class文件,编译后的字节码文件格式主要分为两部分:常量池和方法字节码。
常量池记录的是代码出现过的所有token(类名,成员变量名等等)以及符号引用(方法引用,成员变量引用等等);方法字节码放的是类中各个方法的字节码。
相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法.
1.2. String.intern()
检查字符串常量池中是否存在String并返回池里的字符串引用
(1) 若池中存在,那么直接返回池中它的引用;
(2) 若池中不存在,则将其加入池中,并返回其引用。
这样做主要是为了避免在堆中不断地创建新的字符串对象
代码1:
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); //flase
String temp=s.intern();
System.out.println(temp == s2); //true
String s3 = new String("1") + new String("1");
s3.intern();//池中未发现"11",将"11"的引用s3放置在池中,并返回s3引用
String s4 = "11";
System.out.println(s3 == s4); //true
}
注意:虽然String.intern()的返回值永远等于字符串常量。但这并不代表在系统的每时每刻,相同的字符串的intern()返回都会是一样的(虽然在95%以上的情况下,都是相同的)。因为存在这么一种可能:在一次intern()调用之后,该字符串在某一个时刻被回收,之后,再进行一次intern()调用,那么字面量相同的字符串重新被加入常量池,但是引用位置已经不同。
1.3. final修饰的字符串
代码1:
public static void main(String[] args) {
final String a = "hello";
String b = "hello";
final String c = "world";
String d = "hello" + "world";
String e = a + c;
String f = b + c;
String g = "helloworld";
System.out.println(g == d);//true
System.out.println(g == e);//true
System.out.println(a == b);//true
System.out.println(g == f);//false
}
由于final修饰的String变量不可更改,所以,当一个String变量被final修饰时,这个值在编译期就可以确定,所有将该变量直接替换为它对应的值.
在编译期,由于a和c的值已经确定并且不会再更改(效果同d),所以e的值能够在编译期就确定下来,直接指向了常量区的g,前两个均为true;再看下f,由于b值(变量)的不确定性,所以在编译期不能确定其值,只能在运行时确认,所以(g == f)为false。
1.4. 在字符串常量池中的优化
代码1:
public static String str = "laji" + "MySQL";
public static void main(){
}
代码2:
public static void main(String[] args){
String string1 = "hello";
String string2 = "world";
String string3 = string1+string2;
String string4="helloworld";
System.out.println(string3 == string4);//false
}
是不是发现和上边小结代码很相似,只是其中的字符串变量没有被final修饰,出现不同的结果证明了final修饰变量时的作用:变量不可变(被final修饰的变量其实就是常量)
总结:
-
对于直接做+运算的两个字符串(字面量)常量,并不会放入String常量池中,而是直接把运算后的结果放入常量池中.
-
对于先声明的字符串字面量常量,会放入常量池,但是若使用字面量的引用进行运算就不会把运算后的结果放入常量池中
-
总结一下就是JVM会对String常量的运算进行优化,未声明的,只放结果;已经声明的,只放声明
1.5. 字符串常量池在JVM运行时数据区的什么位置
方法区 ,方法区域在逻辑上是堆的一部分
代码1
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
出现异常,heap space
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.Integer.toString(Integer.java:440)
at java.base/java.lang.String.valueOf(String.java:3058)
at com.example.demo.util.Test2.main(Test2.java:11)