根底数据类型替换
这个话题,须要从最最根底的一道题目说起,看题目:以下代码a和b的值会替换么:
public static void main(String[] args) { int a = 1, b = 2; swapInt(a, b); System.out.println("a=" + a + " , b=" + b); } private static void swapInt(int a, int b) { int temp = a; a = b; b = temp; }
后果预计大家都晓得,a和b并没有替换:
integerA=1 , integerB=2
然而起因呢?先看这张图,先来说说Java虚拟机的构造:
运行时区域次要分为:
线程公有:
- 程序计数器:
Program Count Register
,线程公有,没有垃圾回收 - 虚拟机栈:
VM Stack
,线程公有,没有垃圾回收 - 本地办法栈:
Native Method Stack
,线程公有,没有垃圾回收
- 程序计数器:
线程共享:
- 办法区:
Method Area
,以HotSpot
为例,JDK1.8
后元空间取代办法区,有垃圾回收。 - 堆:
Heap
,垃圾回收最重要的中央。
- 办法区:
和这个代码相干的次要是虚拟机栈,也叫办法栈,是每一个线程公有的。
生命周期和线程一样,次要是记录该线程Java办法执行的内存模型。虚拟机栈外面放着好多栈帧。留神虚拟机栈,对应是Java办法,不包含本地办法。
一个Java办法执行会创立一个栈帧,一个栈帧次要存储:
- 局部变量表
- 操作数栈
- 动静链接
- 办法进口
每一个办法调用的时候,就相当于将一个栈帧放到虚拟机栈中(入栈),办法执行实现的时候,就是对应着将该栈帧从虚拟机栈中弹出(出栈)。
每一个线程有一个本人的虚拟机栈,这样就不会混起来,如果不是线程独立的话,会造成调用凌乱。
大家平时说的java内存分为堆和栈,其实就是为了简便的不太谨严的说法,他们说的栈个别是指虚拟机栈,或者虚拟机栈外面的局部变量表。
局部变量表个别寄存着以下数据:
- 根本数据类型(
boolean
,byte
,char
,short
,int
,float
,long
,double
) - 对象援用(reference类型,不肯定是对象自身,可能是一个对象起始地址的援用指针,或者一个代表对象的句柄,或者与对象相干的地位)
- returAddress(指向了一条字节码指令的地址)
局部变量表内存大小编译期间确定,运行期间不会变动。空间掂量咱们叫Slot(局部变量空间)。64位的long和double会占用2个Slot,其余的数据类型占用1个Slot。
下面的办法调用的时候,实际上栈帧是这样的,调用main()函数的时候,会往虚拟机栈外面放一个栈帧,栈帧外面咱们次要关注局部变量表,传入的参数也会当成局部变量,所以第一个局部变量就是参数args
,因为这个是static
办法,也就是类办法,所以不会有以后对象的指针。
如果是一般办法,那么局部变量表外面会多出一个局部变量this
。
如何证实这个货色真的存在呢?咱们大略看看字节码,因为局部变量在编译的时候就确定了,运行期不会变动的。上面是IDEA
插件jclasslib
查看的:
下面的图,咱们在main()
办法的局部变量表中,的确看到了三个变量:args
,a
,b
。
那在main()办法外面调用了swapInt(a, b)呢?
那堆栈外面就会放入swapInt(a,b)
的栈帧,相当于把a和b局部变量复制了一份,变成上面这样,因为外面一共有三个局部变量:
- a:参数
- b:参数
- temp:函数内长期变量
a和b替换之后,其实swapInt(a,b)
的栈帧变了,a变为2,b变为1,然而main()
栈帧的a和b并没有变。
那同样来从字节码看,会发现的确有3个局部变量在局部变量表内,并且他们的数值都是int类型。
而swap(a,b)
执行完结之后,该办法的堆栈会被弹出虚拟机栈,此时虚拟机栈又剩下main()
办法的栈帧,因为根底数据类型的数值相当于存在局部变量中,swap(a,b)
栈帧中的局部变量不会影响main()
办法的栈帧中的局部变量,所以,就算你在swap(a,b)
中替换了,也不会变。
根底包装数据类型替换
将下面的数据类型换成包装类型,也就是Integer
对象,后果会如何呢?
public static void main(String[] args) { Integer a = 1, b = 2; swapInteger(a, b); System.out.println("a=" + a + " , b=" + b); } private static void swapInteger(Integer a, Integer b) { Integer temp = a; a = b; b = temp; }
后果还是一样,替换有效:
a=1 , b=2
这个怎么解释呢?
对象类型曾经不是根底数据类型了,局部变量表外面的变量存的不是数值,而是对象的援用了。先用jclasslib
查看一下字节码外面的局部变量表,发现其实和下面差不多,只是描述符变了,从int
变成Integer
。
然而和根底数据类型不同的是,局部变量外面存在的其实是堆外面实在的对象的援用地址,通过这个地址能够找到对象,比方,执行main()
函数的时候,虚拟机栈如下:
假如 a 外面记录的是 1001 ,去堆外面找地址为 1001 的对象,对象外面存了数值1。b 外面记录的是 1002 ,去堆外面找地址为 1002 的对象,对象外面存了数值2。
而执行swapInteger(a,b)
的时候,然而还没有替换的时候,相当于把 局部变量复制了一份:
而两者替换之后,其实是SwapInteger(a,b)
栈帧中的a外面存的地址援用变了,指向了b,然而b外面的,指向了a。
而swapInteger()
执行完结之后,其实swapInteger(a,b)
的栈帧会退出虚拟机栈,只留下main()
的栈帧。
这时候,a其实还是指向1,b还是指向2,因而,替换是没有起成果的。
String,StringBuffer,自定义对象替换
一开始,我认为String
不会变是因为final
润饰的,然而实际上,不变是对的,然而不是这个起因。起因和下面的差不多。
String
是不可变的,只是说堆/常量池内的数据自身不可变。然而援用还是一样的,和下面剖析的Integer
一样。
其实StringBuffer
和自定义对象都一样,局部变量表内存在的都是援用,所以替换是不会变动的,因为swap()
函数内的栈帧不会影响调用它的函数的栈帧。
不行咱们来测试一下,用事实谈话:
public static void main(String[] args) { String a = new String("1"), b = new String("2"); swapString(a, b); System.out.println("a=" + a + " , b=" + b); StringBuffer stringBuffer1 = new StringBuffer("1"), stringBuffer2 = new StringBuffer("2"); swapStringBuffer(stringBuffer1, stringBuffer2); System.out.println("stringBuffer1=" + stringBuffer1 + " , stringBuffer2=" + stringBuffer2); Person person1 = new Person("person1"); Person person2 = new Person("person2"); swapObject(person1,person2); System.out.println("person1=" + person1 + " , person2=" + person2); } private static void swapString(String s1,String s2){ String temp = s1; s1 = s2; s2 = temp; } private static void swapStringBuffer(StringBuffer s1,StringBuffer s2){ StringBuffer temp = s1; s1 = s2; s2 = temp; } private static void swapObject(Person p1,Person p2){ Person temp = p1; p1 = p2; p2 = temp; }class Person{ String name; public Person(String name){ this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; }}
执行后果,证实替换的确没有起成果。
a=1 , b=2stringBuffer1=1 , stringBuffer2=2person1=Person{name='person1'} , person2=Person{name='person2'}
总结
根底数据类型替换,栈帧外面存的是局部变量的数值,替换的时候,两个栈帧不会烦扰,swap(a,b)
执行实现退出栈帧后,main()
的局部变量表还是以前的,所以不会变。
对象类型替换,栈帧外面存的是对象的地址援用,替换的时候,只是swap(a,b)
的局部变量表的局部变量外面存的援用地址变动了,同样swap(a,b)
执行实现退出栈帧后,main()
的局部变量表还是以前的,所以不会变。
所以不管怎么替换都是不会变的。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。集体写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜爱题目党,不喜爱花里胡哨,大多写系列文章,不能保障我写的都完全正确,然而我保障所写的均通过实际或者查找材料。脱漏或者谬误之处,还望斧正。
2020年我写了什么?
开源编程笔记
素日工夫贵重,只能应用早晨以及周末工夫学习写作,关注我,咱们一起成长吧~