一、字符串常量池
JVM的运行时内存可以分为堆、方法区、程序计数器、虚拟机栈和本地方法栈。
而在方法区中有一个字符串常量池,用来保存字符串这个不可变量。如果我们使用String str=new String(“java虚拟机”)来new一个string对象,则该对象的实例保存在堆中。
如果我们使用String str="java虚拟机"来创建一个字符串,jvm首先会在字符串常量池中创建该String的实例,然后将常量池中该实例的引用返回给str。
new出来的String保存在堆中,如果我们想让字符串常量池中也保存该string的实例呢?可以使用String.intern()这个方法将字符串复制到常量池中,返回在常量池中的引用。
二、intern方法在1.7和1.6及之前差异
字面量:指的是String的值,两个字符串equals为true,字面量相同
jdk1.6及之前
如果常量池存在该字面量的字符串,返回这个常量池的对象引用
常量池不存在这个字面量的字符串,a.intern()会常量池创建字面量一样的字符串,返回常量池(新建)的对象引用
jdk1.7及之后
如果常量池存在该字面量的字符串,返回这个常量池的对象的引用(同1.6)
如常量池不存在字面量的对象,在常量池中记录首次出现的实例引用。调用intern()返回这个引用。
三、代码验证
3.1 StringTest1
package com.web;
public class StringTest1 {
public static void main(String[] args) {
String s1=new String("123");
String internS1 = s1.intern();
String poolsS1 = "123";
System.out.println("s1堆上字符串 与 internS1的字符串:"+(s1 == internS1));
System.out.println("s1常量池字符串 与 internS1的字符串:"+(poolsS1 == internS1));
System.out.println("s1常量池字符串 与 s1堆上字符串:"+(poolsS1 == s1));
System.out.println("--------------------------------------------------------");
String s2=new String("zhangsan")+new String("feng");//执行完这行代码后,常量池中会有"java"和"虚拟机",但是不会有"java虚拟机"。
String internS2 = s2.intern();
String poolsS2 = "zhangsanfeng";
System.out.println("s2堆上字符串 与 internS2的字符串:"+(s2 == internS2));
System.out.println("s2常量池字符串 与 internS2的字符串:"+(poolsS2 == internS2));
System.out.println("s2常量池字符串 与 s2堆上字符串:"+(poolsS2 == s2));
}
}
3.2 执行结果
3.3 具体分析(都是1.8上运行的)
对于s1
String s1 = new String(“123”);堆上创建123字符串,常量池创建123字符串对象。(第一次运行,之前常量池是空的,没有123字符串)
String internS1 = s1.intern();intern()方法找到常量池上“123”字面量的字符串,返回这个引用
String poolsS1 = “123”;poolsS1 指向常量池创建的123字符串
poolsS1 == internS1 != S1
对于s2
String s2 = new String(“zhangsan”) + new String(“feng”);常量池有"zhangsan"和“feng”字符串对象,堆上“zhangsan”字符串对象。
String internS2 = s2.intern();intern()方法在常量池中记录首次出现的实例(堆上)的引用,返回第一次hello的字符串
poolsS2 == internS2 == S2
3.4 StringTest2(都是1.8上运行的)
public class StringTest2 {
public static void main(String[] args) {
String str1 = new StringBuilder("ali").append("baba").toString();
System.out.println(str1 == str1.intern());
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2 == str2.intern());
}
}
3.5结果
3.6 分析
jdk1.7之后,字符串常量池转移到堆中,则只需要在常量池中记录一下首次出现的实例引用即可,因此intern()方法返回的引用和由StringBuilder创建的字符串实例就是同一个,所以str1 == str1.intern()为true。
但是"java"这个字符串在执行StringBuilder().toString()之前就已经出现过了,不符合intern()"首次遇到"原则,则返回false。
原因:jvm从启动,到执行main里面的第一条代码,要经历很多的,比如加载rt.jar里面所有的Class,加载一个class肯定要执行static{}中内容,况且rt.jar中的jdk的类里面有很多xxx.startWith(“java”)或者其他用到"java"的代码,jvm启动的时候直接按照常量加载进来了丢到internmap里面了。
四、方法参数传递机制
Person
package com.example.demo;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@Getter
@Setter
public class Person {
private Integer id;
private String personName;
public Person(String personName) {
this.personName = personName;
}
}
TestTransferValue
package com.example.demo;
public class TestTransferValue {
public void changeValue1(int age) {
age = 30;
}
public void changeValue2(Person person) {
person.setPersonName("XXX");
}
public void changeValue3(String str) {
str = "XXX";
}
public static void main(String[] args) {
final TestTransferValue testTransferValue = new TestTransferValue();
int age = 20;
testTransferValue.changeValue1(age);
System.out.println("age------------" + age);
final Person person = new Person("abc");
testTransferValue.changeValue2(person);
System.out.println("person------" + person.getPersonName());
String str = "abc";
testTransferValue.changeValue3(str);
System.out.println("person------" + str);
}
}
测试结果
结果分析
age定义的位置是局部变量,基本数据类型传的是变量的副本,changeValue1不会改变原始变量的值。
person引用数据类型传的是对象的引用,(指向同一个对象),changeValue2会改变变量的值。
String 类型的引用类型存放在字符串常量池中,有则用之,无则创建。
参考文章
https://www.jianshu.com/p/8966c51e9728
https://www.jianshu.com/p/be66e22f5fc8
https://blog.csdn.net/w1764662543/article/details/95360095
https://www.cnblogs.com/wa1l-E/p/14216386.html