栈 堆 方法区 常量池
栈
存放java在函数中定义的基本类型的变量的引用和数据,以及对象的引用都放在栈中存储。
堆
主要存放new出来的对象和数组。
方法区
存储已经被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码等数据。
静态变量、常量在方法区;所有方法,包括静态和非静态的,也在方法区。
常量池
静态常量池
静态常量池存在于class文件中。
运行时常量池
运行时常量池,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池值的是运行时常量池。所以呢,讨论的都是运行时常量池。
字符串常量池
String a = "abc";
String b = new String("abc");
System.out.println(a==b);
结果:false
对象b储存在堆中,这个是不用质疑的,
而a作为字面量一开始储存在了class文件中,之后运行期,转存至方法区中。
它们两个就不是同一个地方存储的。
实例
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true; s1 和 s2 都指向了方法区常量池中的Hello。
System.out.println(s1 == s3); // true; 因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true
System.out.println(s1 == s4); // false; s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
System.out.println(s1 == s9); // false; 在JAVA9中,因为用的是动态调用,所以返回的是一个新的String对象。所以s9和s4,s5这三者都不是指向同一块内存
System.out.println(s1 == s6); // true; 归功于intern方法,这个方法首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的.
特例1
public class test {
public static final String a = "123";
public static final String b = "456";
public static void main(String[] args) {
String c = "???";
String d = a + b;
System.out.println(c = d);
}
}
// 我们可以发现,
// 对于final类型的常量它们已经在编译中被确定下来,
// 自动执行了+号,把它们拼接了起来,所以就相当于执行了"123" + "456";
特例2
public class test2 {
public static final String a;
public static final String b;
static {
a = "123";
b = "456";
}
public static void main(String[] args) {
String c = "123456";
String d = a + b;
System.out.println(c == d);
}
}
// 上个例子是在编译期间,
// 就已经确定了a和b,但是在这段代码中,
// 编译期static不执行的,a和b的值是未知的,
// static代码块,在初始化的时候被执行,初始化属于类加载的一部分,
// 属于运行期。看看反编译的结果,很明显使用的是indy指令,
// 动态调用返回String类型对象。
// 一个在堆中一个在方法区常量池中,自然是不一样的。
本文来自博客园,作者:jsqup,转载请注明原文链接:https://www.cnblogs.com/jsqup/p/16116022.html