1. 声明
根据String的构造器,声明一个字符串对象的形式有两种:
- 字面量形式
String str = "hello";
- 使用new关键字
String str = new String("hello");
2. 内存分布
在将上述两种声明方式区别的时候,我们先介绍一下JDK1.8之后jvm内存分布:
详细请阅读《深入理解Java虚拟机》
- 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖该计数器完成。
由于Java虚拟机的多线程是通过线程流转切换、分配处理器执行时间来实现,在任何一个确定的时间,一个处理器(多核处理器的话就是一个内核)都只会执行一条线程的指令。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各线程之间互不影响、独立存储,所以该内存是 线程私有
。
- 虚拟机栈
线程私有
,生命周期与线程相同。Java方法执行时使用,存放了局部变量表(基础数据类型、对象引用等)、操作数栈、动态连接、方法出口等信息。
- 本地方法栈
类似虚拟机栈,本地方法栈是用于执行本地方法(Native Method)的。《JVM规范》中对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,不同的虚拟机可以按需实现。
- 方法区
线程共享
,用于存储已经被虚拟机加载的类型信息、常量、静态变量、及时编译器编译后的代码缓存等数据。
- 堆
是JVM内存中最大的一块。线程共享
,在虚拟机启动的时候创建。唯一的目的就是存放对象实例。
- 运行时常量池
Java6和6之前,常量池是存放在方法区(永久代)中的。
Java7,将常量池是存放到了堆中。
Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中。
3. 字符串常量池
字符串常量池是Java语言中的一个概念,它是Java虚拟机内部的一种数据结构,用于管理字符串字面值。字符串字面值是指在源代码中直接写出的字符串常量。
Java虚拟机会将所有的字符串字面值存储在字符串常量池中。
- 使用字面量创建新的字符串对象时,Java虚拟机会先检查字符串常量池中是否已经存在具有相同内容的字符串。如果存在,那么它就会返回对已有字符串的引用,而不是创建一个新的字符串对象。这个过程称为字符串的intern操作。
- new关键字创建时,会直接在堆里创建一个新对象,变量所引用的都是这个新对象的地址,如果new的对象内容在常量池中已经有了,那么会由堆在指向常量池的字符串;但是如果常量池中没有的话不会额外在常量池维护。
- 使用变量表达式(+、+=)来创建对象的话,不仅会检查字符串常量池,还会在堆里创建这个对象,最后引用是指向堆里的对象。
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
String str4 = "he" + "llo"; // 编译器确定为常量,直接到常量池中引用
String str5 = "he";
String str6 = str5 + "llo"; // 编译器不能确定为常量,在堆中创建一个对象后在去检查常量池
System.out.println(str2 == str1);//true
System.out.println(str3 == str1);//fasle
System.out.println(str3 == str2);//fasle
System.out.println(str4 == str1);//true
System.out.println(str6 == str1);//fasle
System.out.println(str6 == str4);//fasle
4. intern()方法
这是一个本地方法,当调用intern()方法时,如果池中包含一个与该字符串内容相同的字符串时,则返回该字符串。否则将该字符串对象加入到池中,并返回该对象的引用。
通俗的说:调用String对象的intern()方法,如果常量池中有对象则直接返回该字符串的引用(存在堆中就返回堆中,存在池中就返回池中),如果没有则将该对象加入到池中返回池中的引用。
String str1 = "hello";//字⾯量 只会在常量池中创建对象
String str2 = str1.intern();
System.out.println(str1==str2);//true
String str3 = new String("world");//new 关键字只会在堆中创建对象
String str4 = str3.intern();//返回池中的引用
System.out.println(str3 == str4);//false
String str5 = str1 + str2;//变量拼接的字符串,会在常量池中和堆中都创建对象
String str6 = str5.intern();//这⾥由于池中已经有对象了,直接返回的池中引用
System.out.println(str5 == str6);//true
String str7 = "hello1" + "world1";//常量拼接的字符串,只会在常量池中创建对象
String str8 = str7.intern();
System.out.println(str7 == str8);//true