1.JVM内存区域的简单划分
- 我之所以把方法区画地这么小,是因为优化后的JDK已经逐渐减少了对方法区的使用,现在字符串常量池都是放在堆内的,详情请参见博文:
Java 字符串常量池到底是在PermGen方法区、是在heap堆里面、还是在Metaspace 元空间里面呢?
2.基本数据类型与引用数据类型
int num = 10;
double dec = 1.23;
- 上述基本类型在内存中是这样的:
int[][] twoDim = new int[][]{{6, 6, 6},
{7, 7, 7}};
- 上述表达式完全合法,你可以去试试。
- 数组也是引用类型,以二维数组为例查看引用类型在内存中的状态:
3.现在我们来传参数
public static void main(String[] args){
int num = 10;
double dec = 1.23;
plus(num, dec);
}//main方法结束
public static void plus(int num, double dec){
num++;
dec--;
}//plus方法结束
- 上述传递参数的过程是这样的:
- 由于传递全部发生在栈内,栈变量的变量名与数值是一对一的关系,一个改变与另一个无关,传递的仅仅是栈变量存储的值。
public static void main(String[] args){
int[][] twoDim = new int[][]{{6, 6, 6},
{7, 7, 7}};
pass(twoDim);
}//main结束
public static void pass(int[][] twoDim){
twoDim[0][0] = 0;
twoDim = null;
}//pass方法结束
- 上述引用类型传递参数是这样的:
- 这个所谓的“地址传递”,本质上也是栈变量的值传递,与基本数据类型的值传递是相通的机制,区分开反而加深了误解。
- 应该区分的是栈变量 与 堆对象,引用类型的数据都存放在堆中,栈内存放的是对堆数据的引用。本质上,是数学“一对一”和“一对多”的问题,栈变量的变量名与其代表的数据一 一对应,都是相互独立的,任何一个改变都不影响其它。引用类型的数据空间在堆,数据引用在栈,并且一个堆数据可以对应多个栈引用,这多个栈引用都可以访问堆数据,无论是谁顺着地址去改变堆数据的内容,其它栈引用都能感知到这个变化,请注意,栈引用本身也是一个栈变量,如果仅仅对这个栈变量本身进行改变,而不顺着地址去改变堆内容,那么就不会影响其它栈引用,比如array = null ; 就是一个只针对引用本身的改变。
4.我们来看看String的传递
- 有人说,String是个特例,先来看看
public static void main(String[] args) {
String string = "我会改变吗?";
System.out.println(string);//我会改变吗?
MethodParameter thisClass = new MethodParameter();
thisClass.changeString(string);
System.out.println(string);//我会改变吗?
}//main方法结束
public void changeString(String string){
string = "我改变了!";
System.out.println("这是changeString内的输出:" + string);//我改变了!
}
//以下是输出结果
我会改变吗?
这是changeString内的输出:我改变了!
我会改变吗?
- 有人以类似的例子来作为String是例外的证据,但这是理解偏差导致的
- String真正的特殊之处在于:
任何双引号包起来的字符串都是一个String对象,而不是一个值,“我是String对象”,这些对象保存在字符串常量池中。 - 因此,方法内的这句话:
public void changeString(String string){
string = "我改变了!";
}
只是把本方法的栈变量string
原本指向"我会改变吗?"
的地址,改成了指向"我改变了!"
的地址,但是并没有沿着原本的地址去改变原本的数据空间的内容,因此main方法内的string根本就不会改变。
- 事实上,要想顺着地址去改变,只能在方法中这样写:
public void changeString(String string){
char[] newString = {'我', '改', '变', '了', '!'};
string.value = newString;
//这个 .(点) 才叫顺着地址去改变数据空间的内容
}
- 但是上面能成功吗?我们来瞅一眼String的源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
// ……
}
- 看到final你就该明白,上述顺着地址去改变内容的行为是会报错的,因为被final修饰的变量一经赋值便不能再改变。
- 因此,如果不断地使用字符串,那就会不断地消耗存储空间,因为每次都是一个final修饰,而且,字符串又是最常用的引用类型,一个较大的程序很快就会因为内存耗完而崩溃;正是基于此需求,字符串常量池技术才应运而生,相同的字符串,如果不是显性要求重开数据空间,都是会保留一个对象数据空间,保留多个对该对象的引用,从而大大减少内存的消耗。
5.写在最后
画图不易,梳理更难,如果对你有帮助,请给个赞,谢谢^ V ^