Class常量池、运行时常量池、字符串常量池

文章目录

    • 1、引言
    • 2、Class常量池
    • 3、运行时常量池
      • 3.1 基本类型的包装类
      • 3.2 String.intern()
    • 4、字符串常量池
      • 4.1 直接用双引号创建字符串
      • 4.2 使用new关键字创建字符串
      • 4.3 组合
      • 4.4 总结

1、引言

Class常量池、运行时常量池、字符串常量池_第1张图片

2、Class常量池

Class常量池、运行时常量池、字符串常量池_第2张图片

  • .java文件被编译成 .class文件后,在类加载时,将.class文件提取在方法区的Class文件中,在Class文件中生成我们所说的Class常量池
  • jvm的方法区里存放着类的版本,字段,方法,接口和常量池,常量池里存储着字面量和符号引用
    Class常量池、运行时常量池、字符串常量池_第3张图片

Class常量池存放的是 从.class文件中得到的字面量值,符号引用。而运行时常量池就是把所有的Class常量池里的数据汇总到一起,将符号引用替换为直接引用。
Class常量池、运行时常量池、字符串常量池_第4张图片

3、运行时常量池

  • 相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译期间放入常量,运行期间的常量也可以加到常量池中,这里的常量包括:基本类型的包装类和String(使用String.intern())

3.1 基本类型的包装类

  • java中的基本类型的包装类(Byte、Short、Integer、Long、Character这5种)都实现了常量池技术,默认创建[-128,127]的相应类型的缓存,但是,超出此范围仍然会去创建新的对象。 浮点数类型的包装类Float、Double并没有实现常量池技术

需要注意的是:

  • 使用new,仍然会创建新对象. 比如 Integer i1 = new Integer(40)
  • Integer a = 40在编译的时候会直接将代码封装成Integer a =Integer.valueOf(40),从而使用常量池中的对象
public void fun07(){
     
		Integer a = 10;
		Integer b = 10;
		System.out.println(a == b); // true
		Integer c = 200;
		Integer d = 200;
		System.out.println(c == d); // false
		Long e = 200L;
		Long f = 200L;
		System.out.println(e == f); // false
		Long g = 20L;
		Long h = 20L;
		System.out.println(g == h); // true
		Double i = 20.0;
		Double j = 20.0;
		System.out.println(i == j); // false
}

3.2 String.intern()

​当调用 String 的 intern() 方法时,若字符串常量池中已经存在这个String对象,则返回字符串常量池中此对象的引用;否则,将此String串加入字符串池中,并返回该String对象的引用,简单来说,intern()就是用来返回常量池中的某字符串。对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intren() == t.intern() 才为true。

4、字符串常量池

  • 字符串常量池,即为了避免多次创建字符串对象,在jvm中开辟一块空间储存不重复的字符串
  • 直接使用双引号""声明字符串时, java都会去常量池找有没有这个相同的字符串,如果有,则将常量池的引用返回给变量; 如果没有,会在字符串常量池中创建一个对象,然后返回这个对象的引用

public static void main(String[] args){
  String name1 = “chenliang”;
  String name2 = “chenliang”;
  System.out.println(name1 == name2); // true
} ​
采用字面量的方式创建一个字符串时,JVM首先去字符串常量池中去查找是否存在"chenliang"这个对象,如果不存在,则在字符串常量池中创建"chenliang"对象,然后将该对象的引用地址返回给字符串常量name1,则此时name1会指向字符串常量池中"chenliang"这个对象,若存在,则不创建任何对象,直接将字符串常量池中"chenliang"对象的引用地址返回,赋值给字符串常量。

  • 使用new关键字创建,比如String a = new String(“hello”),这里可能创建两个对象。如果字符串常量池没有,则在这里创建一个对象。另一个是必须创建的,new 关键字必然会在堆中创建一个新对象,最终返回的是new 关键词创建对象的地址

public static void main(String[] args){
  String name3 = new String(“chenliang”);
  String name4 = new String(“chenliang”);
  System.out.print(nam3 == name4); // false
} ​
采用new关键字去新建一个字符串对象时,JVM首先在字符串池中查找是否存在"chenliang"这个字符串对象,若有则不在字符串池中再去创建"chenliang"对象,而是直接在堆中创建"chenliang"字符串对象,然后将堆中该对象的引用地址返回给name3,若字符串池中没有该对象,则会在字符串池中创建一个该对象,再去堆中创建一个"chenliang"对象,并将堆中该对象的引用地址返回给name3。

  • 在jdk1.6及之前,字符串常量池是属于运行时常量池。在jdk1.7 ,字符串常量池从方法区中被单独拿到堆中

4.1 直接用双引号创建字符串

String s1= "ram";
String s2= "ram";
String s2= "ram";
  • 当我们第一次执行String s1 =“ram”时,JVM将在字符串常量池中创建一个新对象,s1将引用该对象,即“ram”。
  • 当我们第二次执行String s2 =“ram”时,JVM将检查字符串常量池中是否存在任何值为“ram”的对象。截至现在是的,我们已经在“字符串常量”池中存在“ram”,因此它不会创建新对象,只是s2引用变量将指向现有的“ram”对象。
  • 对于String s3 =“ram”,将发生相同的过程。

4.2 使用new关键字创建字符串

String str1 = new String("mohan");
  • jvm第一步会检查字符串常量池中是否有"mohan",,发现没有,创建一个新对象在常量池中。因为有new关键词,会在堆中创建对象。然后将这个对象的地址引用返回给str1

4.3 组合

String st1 = "rakesh";
String st2 = new String("rakesh");
  • 当我们执行String st1 =“rakesh”时,JVM将在字符串常量池中创建一个对象,st1将引用它
  • 执行第二步的时候,JVM将检查字符串常量池中是否有任何可用的名称为“rakesh”的对象,现在是的,我们已经在字符串常量池中使用了“rakesh”,因此JVM不会在字符串常量池中创建任何对象。因为有new关键词,它将在堆中创建一个对象,st2将指向该对象。

当我们使用new运算符创建String对象时,JVM将首先在SCP(字符串常量池)中检查,该对象是否可用。如果SCP内部没有该对象,JVM将创建两个对象,一个在SCP内部,另一个在SCP外部。但是如果JVM在SCP中找到相同的对象,它只会在SCP外部创建一个对象。

public class StringExample1 {
     
	public static void main(String[] args) {
      
		String s1 = "india";				// ①
		
		String vs2 = "indiais";
		String s2 = s1 + "is";				// ②
		System.out.println(vs2==s2);		// false
		System.out.println(vs2.equals(s2));	// true
		
		String k = "indiagreat";
		String ks = s1.concat("great");		// ③
		System.out.println(ks==k);			// false
		System.out.println(ks.equals(k));	// true
		
		String m = "indiaisindia";
		String ms = s2.concat(s1);			// ④
		System.out.println(ms==m);			// false
		System.out.println(ms.equals(m));	// true
		
		String vs1 = "indiacountry";
		s1 += "country";					// ⑤
		System.out.println(vs1==s1);		// false
		System.out.println(vs1.equals(s1));	// true
	}
}
  • ① "india"会直接在scp(字符串常量池)中创建
  • ② 对于 s1 + “is”,因为编译器无法知道s1是什么,而字符串String是一个不可修改的类,所以这里的 " + " 会被编译成:String s2 = (new StringBuilder(“india”)).append(“is”).toString(),会在堆中new一个 "indiais"字符串。(因为"is"是双引号声明的,所以同样在scp中创建一个"is"对象)

StringBuilder的toString方法,会在堆中重新new一个字符串对象
Class常量池、运行时常量池、字符串常量池_第5张图片

  • ③ String ks = s1.concat(“great”)会在堆中new一个"indiagreat"字符串。(因为"great"是双引号声明的,所以同样在scp中创建一个"great"对象)
  • ④ String ms = s2.concat(s1)同理,会在堆中new一个"indiaisindia"字符串
  • ⑤ s1 += “country” 也就是 s1 = s1 + “country”,和②一样,在堆中new一个"indiacountry"字符串

4.4 总结

  • 对于双引号""直接声明的字符串,比如String a = "aa",会直接在scp中创建对象
  • 对于两个双引号""声明的字符串使用 " + " 拼接,jvm在编译时就去掉其中的加号,直接将其编译成一个相连的结果存入了常量池,而不是等到运行时再去进行相加,但是两个声明的字符串不会放进常量池(String s = “abc”+ “def”, 是将“abcdef"放入字符串常量 而不把 “abc”与"def"放进常量池)

String s = “a” + “b”;
System.out.println(s == “ab”); // true
即 String s = “a” + “b”;只创建了一个对象,就是ab,存于常量池中。

  • 对于其中有一个不是双引号""声明的字符串,变量相加时,编译器无法得知结果,会用StringBuilder创建新对象,不会将这个临时结果放到scp中,而是最后在堆中new一个新的对象

String s1 = “a”;
String s2 = s1 + “b”;      // 堆上新建字符串"ab"
String s3 = “a” + “b”;      // 将"ab"存入字符串常量池
String s4 = s1.concat(“b”);   // 堆上新建字符串"ab"
System.out.println(s2 == “ab”);  // false
System.out.println(s3 == “ab”);  // true
System.out.println(s4 == “ab”);  // false
System.out.println(s4 == s2);   // false

参考:https://blog.csdn.net/zzzgd_666/article/details/87999870
参考:https://my.oschina.net/u/867830/blog/1609952
参考:https://www.jianshu.com/p/8966c51e9728
参考:https://segmentfault.com/a/1190000022743814

你可能感兴趣的:(Java)