黑马程序员技术交流社区
标题: Java变量在内存中的分配详解—没读过书的都能看得懂 [打印本页]
作者: 戴振良 时间: 2013-1-28 17:27
标题: Java变量在内存中的分配详解—没读过书的都能看得懂
本帖最后由 戴振良 于 2013-1-28 17:17 编辑
看了大家画的图,意思是有了,可我觉得没人画出来可以很容易让大家理解的,因此我在这里再详细的说说Java变量在内存中的分配情况。(注:有些同学理解能力很强,看老毕视频后已经理解的很清楚了,那就没必要看这贴子,因为我这也是看了老毕的视频后自己经过试验的理解)
学Java,必定常会用到赋值,其实如果我们把赋值操作理解清楚了,那么Java变量在内存的分配情况就清楚了。在这里我说说我对赋值的理解,不一定对,大家多多批评指正:
内存赋值、赋值,得有值,赋到哪去啊,很多人知道是赋到变量去,在这里我觉的大家可以试着改变一下不使用别人定义好的名词,这样更容易理解事情的本质,比如说把值赋给了变量x,如把1赋给了x,x是个什么东西呢?它是变量,这个比较抽象不好理解,我们说把一棵巧柯立放进一个盒子里,那么盒子是那个四四方方的东西,我们看的见所以容易理解,盒子只是那东西的名字,变量看不见,说变量不容易理解,既然变量能装”值”,它必定是一种能装值的东西的名字而已,那它是什么东西的名字呢,答:内存的名字。 变量是内存的名字这个大家都知道,只是思考问题的时候就模糊了.
内存地址再来看x = 1,那么这个x变量我们看不到呀,老说把1存到了x里,我觉得这样对于我们理解不太好,应该这样理解,x = 1,是把1这个值存到了变量x所绑定的内存地址里,因为大家都知道,电脑的所有一切数据,要么放在硬盘、U盘、光盘等,要么放在内存里,所以我们说把1放在内存里,这样比较容量理解本质。把1放在内存里,要有个条件——“必须要先知道要存到内存哪个地方”,打个比方,我们说把巧柯立放进盒子里,必须要先知道盒子在哪里吧!有些没怎么学过电脑的人会说内存就一块,地址就是主板那块内存,地址就一个,这是不对的,一块容量为2G的内存条,它就相当于一个海南省,它里面有好多的地方(地址),而一块4G的内存条就好比一个四川省,它的地方(地址)更多了。当然了,2G和4G的内存的容量大小虽然不同,可是他们的物理形状是一样大小的,至于为什么它们里面的容量(地址)的大小不同,这就是高科技的结果了,没必要去深究了。我们不说把1放在变量p中而说把1放在内存中,这样比较容易理解本质。
值 赋值、赋值,再来说说这个值,这值又是些什么东西呢,看过老毕基础视频的都知道,电脑的一切数据都是010101110110这样的二进制数,那么说简单一点,值就是8种基本数据类型的值,如int、float、char等,再简单点说值就两样东西,数字(如1、2、3.5等)和字符(如’a’,’b’,’c’等),再简单点说值就一样东西,二进制数据(如01010101)。
值、内存、内存地址,应该大家都是知道的,好了,到这里已经废话连编了,这里总结一下,我们一说到赋值,分析时就要想到3样东西:值、内存、内存地址,花了这么多废话给大家讲这个大家都知道的东西,我只是想告诉大家这三个非常重要。
到此,我自己总结了一个结论,应该是不对的,大牛们常说“一切都是对象”,那么我来一句“一切都是赋值操作”。有了这个理论我们分析内存就简单了,因为我上面说赋值就3样东西:值、内存、内存地址,因此我们了解好这3样东西就够了
再来一个我自己的总结:所有的变量(包括基本类型变量和对象类型变量)只能保存8种基本类型的值,不能保存对象。
好了,变量不能保存对象,那对象保存到哪去啊???想不到原因的是因为我们学了基础知识却不会拿来用,我上面说了电脑里所有的数据,要么保存在硬盘、U盘、光盘等,要么保存在内存条,所以对象当然是保存在内存中了。这个思想我们要熟记。来看下面的问题:
Personp1 = new Person();
Personp2 = p1;
PersonP3 = p1;
第一句代码执行后,我们会说p1就是一个Person对象,3句代码都执行完后,有人会说有3个Person对象。
根据我上面总结的理论,我说变量只能保存值,所以p1没有保存对象,所以p1不是Person对象,p1只是一个对象类型的变量,它保存了一个值,但这个值不是Person对象。
说有3个Person对象,我再总结一句:相同的对象,在任何时候都只有一个,而且它在内存的位置不变。
作者: 戴振良 时间: 2013-1-28 17:27
本帖最后由 戴振良 于 2013-1-28 17:23 编辑
上面说的都是一堆废话,因为是我自己总结的,我相信很多人看不懂,没关系,下面开始画图了,有图一定So esay!
我上面说了什么是值,还有对象没说,那么什么是对象呢?我们知道数组是对象,其实本质上他也是一些值,对象一般一创建就有不只一个值,画个内存图分析如下:
int[] x = new int[2];
int[] x 这句代码在栈内存中申请了一个空间,这个空间专门用来保存数组的首地址
从上图,我们可以看到声明变量x后,在内存申请了一块空间,这时里面什么都没有存。变量x在栈内存中的地址是:0xf8a4
接下来看new int[2]的内存图:
从上图中我们可以看到其实对象跟变量差不多,都是分配内存空间,只不过对象分配空间时一般是一个对象就分配好几个连续内存空间。
我们知道数组元素中的默认值为0,数据存在内存中应该是01010101010这样的二进制形式,为了方便我就写数字0了,还有内存的地址,它实际上也是010110101001的形式,为了方便,我们一般把二进制转换成16进制来表示(如0x6ac5),这样书写就比较方便。
从上图,我们可以看到这个对象有两个地存地址,那么第一个0x6ac5就叫做 new int[2]这个对象的首地址。这个对象在堆内存中申请好空间后就把它的首地址0x6ac5的值赋给了变量x,也就是保存到了栈空间内存地址为0xf8a4的内存里。我的理论说变量只能保存值,这里变量x保存的内存地址0xf8a4,它也是一个值,因为我们知道把它转换成10进制后,它就是856这样形式的值(这里是打比方,转换后不是这个值)。
new int[2]对象成功申请内存空间后,把首地址赋给了变量x,内存图哪下:
有了数组对象的内存图,我假设所有对象的内存图与数组对象的一样,如下:
class Person {
String name = "张三";
int age = 250;
}
上面有两个Person对象,虽然他们的内容一样(名字和年龄),可是他们不是同一个对象。把上图换成代码形式为:
Person p = 0x5501;
Person p2 = 0x7301;
所以我说任何的变量保存的都是值,只是对象类型的变量保存的值是一个数值,这个数值还有另外的含意,它是一个对象的首地址。
首地址
再来说说对象的这个首地址,我们的变量为什么保存了首地址就能访问整个对象了呢?(访问对象中的所有地址),打个比方,老毕、老张、老王、老方,他们住在学校宿舍楼里,分别住01、02、03、04号房,那么我们只要知道了老毕住01号房的地址,我们要访问老张、老王、老方,都是轻而易举的事了。大家看我们上面画的内存图,同一个对象中的多个成员他们的内存地址是相连的,所以变量只保存对象的首地址就可以了。
作者: 戴振良 时间: 2013-1-28 17:27
本帖最后由 戴振良 于 2013-1-28 17:58 编辑
接下来再讲讲一切都是赋值操作就结束本贴吧class Person {}
class QuoteDemo {
public static void main(String[] args) {
Person p1 = null;
newPerson(person);
System.out.println(p1);
}
public static void newPerson(Person p2) {
p2 = new Person();
}
}
main方法中调用newPerson(person);可以变化下面的赋值语句
Person p2 = p1;
p2 = new Person();
我们把这两句代码和main方法中的放到一起为:
Person p1 = null;
Person p2 = p1;
p2 = new Person();
System.out.println(p1);
用我的理论,不用画内存图也能分析出结果了,p1是变量,那么它保存的是基本类型的值,null能赋值给p1,那么它也是个对象,就算不是,我就当它是吧,我假如null对象在堆内存中的首地址为:0x0000;我假如“new Person()”语句产生的Person对象在内存的首地址为:0x0001,那么放到代码中就是:
Person p1 = 0x0000;
Person p2 = p1(0x0000);
p2 = 0x0001;
System.out.println(0x0000);//0x0000是null的内存地址,所以这里打印null
再来分析上次我出的例子:
例子1class Demo {
public static void main(String[] args) {
ArrayList<String> al = fun();//第1句代码
String s = al.get(0); //第7句代码
System.out.println(s); //第8句代码
}
public static ArrayList<String> fun(){
ArrayList<String> al = new ArrayList<String>();//第2句代码
String s = "张三" ; //第3句代码
al.add(s); //第4句代码
s = "李四"; //第5句代码
return al; //第6句代码
}
}
运行后打印的结果是: 张三 ,在没有画内存图之前我不知道大家是怎么思考的,我当时是这样思考的:如果输出的是张三,说明是把“张三”这对象保存到了集合中,如果输出的是“李四”那么说明是把s这个变量保存到了集合中。 不知道大家是不是这样思考的,如果是,我可以说这样的思考是绝对的错误的。根据我上面的理论,一切都是赋值操作,变量是不能保存对象的,还有同一个对象在任何时都只有一个,而且位置不变,所以“张三”这个对象一产生就只有一个,所以不能说把“张三”这个对象添加到了集合中。说把变量s添加到了集合中也是错误的,原因往下看:
作者: 戴振良 时间: 2013-1-28 17:27
本帖最后由 戴振良 于 2013-1-28 18:05 编辑
我数了一下,上面的代码运行时,总共要执行8句代码,我们一句一句的分析:
首先从main方法执行:1、 执行第一句代码:ArrayList<String> al = fun();
ArrayList<String> al这里声明了一个变量al,内存图如下:
再来看fun();,这里就进入了方法内部执行代码,首先执行方法代码:ArrayList<String> al = new ArrayList<String>();
2、执行第二句代码:ArrayList<String> al = new ArrayList<String>();
这句代码的左边的ArrayList<String>al,在栈内存中声明了一个变量al,内存图如下:
从上图可看到这两个变量都没有值 再看代码的右边: new ArrayList<String>();在堆内存中创建集合对象,内存图如下:
从上图可看到这时集合中没有保存任何东西。该集合对象的首地址是:0x3434
ArrayList<String> al = newArrayList<String>();这一整句代码的内存图为:
3、 执行第三句代码:String s ="张三" ;在栈内存为变量s申请了内存空间,在堆内存为对象“张三”申请内存空间。内存图如下:
这里“张三”这个对象我就不管什么常量池这些了,我假设它也在堆内存中
4、执行第4句代码:al.add(s);,我的理论说一切都是赋值操作,这里就可以用上了,我们不要说把一个s对象添加到了集合中,这里也是赋值,把s的值(0x8888)赋给了集合中的某个变量保存起来了,当然我们不知道这个变量的名字是什么,也不需要知道。内存图如下:
上图可以看到在堆内存中ArrayList对象中有一个变量(这个变量的名字就不用管了)保存了fun方法中变量s的值(0x8888),而这个值是对象“张三”的首地址值,所以这时集合中的这个变量也指向了对象“张三”
5、执行第5句代码:s = "李四";在堆内存中产生一个”李四”对象,然后把这个对象的首地址赋给了变量s,内存图如下:
作者: 戴振良 时间: 2013-1-28 17:34
本帖最后由 戴振良 于 2013-1-28 18:13 编辑
6、执行第6句代码:return al;这里大家千万不要说把一个集合对象al返回给main方法中的变量al。这返回的是al对象的首地址0x3434,一切都是赋值操作,在这里又有体现了,可写成这样:
public static void main(String[] args) {
ArrayList<String> al = 0x3434;
到这里fun方法就执行完了,大家知道方法里的局部变量就会销毁,即fun方法中的变量al、s在内存中被清除了,但是对象“张三”、“李四”还在堆内存中,ArrayList对象中的某个变量保存有对象“张三”的内存地址,而没有任何变量保存有对象“李四”的地址,所以现在的“李四”是个垃圾,它随时有可能被垃圾回收器收走,内存图如下图:
7、执行第7句代码:String s = al.get(0);代码左边“String s”在栈内存申请内存空间,内存图如下:
再看“al.get(0)”,这里我们也不要说是把集合中的第0位置上的对象取出来赋给变量s,我们要改变一下我们以往的思想:任何的集合(包括对象类型的数组)都不能保存对象,只能保存对象的首地址。从上图我们知道变量al现在的值是0x3434,这个值是ArrayList集合对象的内存首地址,通过这个内存地址我们取出来了保存在集合内部0x3435地址中保存的值0x8888,然后是把0x8888赋给变量s,所以这时s的值也指向了对象“张三”,内存图如下:
8、执行第8句代码:System.out.println(s);从上图我们知道现在s中保存的值是0x8888,它是对象“张三”在堆内存中的首地址,因此打印结果是:“张三”。
作者: 戴振良 时间: 2013-1-28 17:34
本帖最后由 戴振良 于 2013-1-28 18:32 编辑
至此练习1的内存图就画完了,从这些图中,可以验证我说的一切都是赋值操作;任何变量(包括对象类型的变量)只能保存值,不能保存对象。
还有我前面说大家可以试着改变一下不使用别人定义好的名词,这样更容易理解事情的本质,大家可以看到我上面的讲解中,我没有使用“引用变量、传值、传址等”一些常用的名词,这理解起来更容易了,因为不用去理解和记这些名词什么意思,还有那些传参数如果传的是基本数据类型,方法就改变不了方法外的变量,如果变量是引用数据类型,方法就可以改变外面的变量等,这些都是让人产生误解的语句。
看完这个内存图,大家再看我上面发的那堆废话,应该就很容易理解了。
能看明白的,真的看明白的给我回复一串1(11111111111111..)好吗?不明白的可以回答一串0,1是真,0是假嘛~~~
下面奉上7个小练习,相信大家都可以轻而易举的画出内存图,并找出输出结果的原因。大家先不要用电脑运行,先自己心算,看看是不是输出自己想要的结果,当你不相信这个结果的时候才用电脑验证一下。
作者: 周志强 时间: 2013-1-28 17:43
先存一下 看看
作者: 黄锦成 时间: 2013-1-28 19:42
最后讲得好
Person p1 = new Person();
Person p2 = p1;
Person P3 = p1;
p1,p2,p3保存的都是new Person();这个对象的内存地址值
作者: 黄锦成 时间: 2013-1-28 19:45
戴振良 发表于 2013-1-28 17:27
上面说的都是一堆废话,因为是我自己总结的,我相信很多人看不懂,没关系,下面开始画图了,有图一定So esa ...
加深印象了。
作者: 黄锦成 时间: 2013-1-28 19:49
戴振良 发表于 2013-1-28 17:34
至此练习1的内存图就画完了,从这些图中,可以验证我说的一切都是赋值操作;任何变量(包括对象类型的变量 ...
1111111111111111111111111111
作者: 黄锦成 时间: 2013-1-28 22:14
苦逼了,画了前3个例子的内存图,先上传上来。剩下的,下次再弄了。
-
test1.PNG
(22.68 KB, 下载次数: 39)
第一题
-
test2.PNG
(35.04 KB, 下载次数: 36)
第二题
-
test3.PNG
(33.37 KB, 下载次数: 39)
第三题
作者: 戴振良 时间: 2013-1-29 11:15
本帖最后由 戴振良 于 2013-1-29 11:27 编辑
黄锦成 发表于 2013-1-28 22:14
苦逼了,画了前3个例子的内存图,先上传上来。剩下的,下次再弄了。
看来你已经掌握了思想,看你test3说的String的内容一旦确定,就不可以改变,以前也听说过,还听说过StringBuffer是可以改变的,于是想到写代码验证一下:
一、String对象的内容确定后就不可以改变了,下面的变量 str1 连接上" world!" 字符串后就变成了另一个对象了。
class Demo8 {
public static void main(String[] args) {
String str1 = new String();
str1 = "hello";
String str2 = str1; //把变量str1中保存的首地址赋给str2
str1 = str1.concat(" world!"); //连接字符串
System.out.println(str1 == str2);//输出:false ,证明这两个变量保存的是两个不同对象的首地址。
System.out.println(str2); //输出:hello
}
}
二、StringBuffer对象的内容确定后还可以改变
class Demo9 {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer();
sb1.append("hello"); //连接字符串
StringBuffer sb2 = sb1; //把变量sb1中保存的首地址赋给sb2
sb1.append(" world!"); //连接字符串
System.out.println(sb1 == sb2);//输出:true ,证明这两个变量保存的是同一个对象的首地址
System.out.println(sb2); //输出:hello world!
}
}
这样一想,String这个对象还是少用吧,因为如果String的内容要改变的话,变一次就在堆内存中多一个对象出来,浪费内存,而用StringBuffer就可以避免这样的现象出现。
作者: 黄锦成 时间: 2013-1-29 11:18
戴振良 发表于 2013-1-29 11:15
看来你已经掌握了思想,看你test3说的String的内容一旦确定,就不可以改变,以前也听说过,还听说过Strin ...
能得到师兄的肯定,很高兴啊。以前学习过C#,说有值类型和引用类型,那时分不清,后来学习java时,也分不清,就专门找一些资料来学习了
作者: 贾文泽 时间: 2013-1-29 12:48
1111111111111111111111111111
作者: 贾文泽 时间: 2013-1-29 13:35
正在回家路上,西安转车呢,下午的车,没事来网吧上会网,没在家里方便,不能做你的题了,可惜了金币哈,,,哈哈,
作者: TheRealBo 时间: 2013-1-30 17:39
awesome
作者: 沈文杰 时间: 2013-1-31 18:17
好帖子,顶起来,受用匪浅
作者: 逍林游 时间: 2013-1-31 18:26
内容有点长···略过···
作者: 曹睿翔 时间: 2013-1-31 19:16
手机实在太悲催,先支持再说,回头看,相信沉不掉
作者: chuanyueing 时间: 2013-2-5 01:13
保存了,稍后再看
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |