引言

在学习Java内存分配的时候就讲过,引用(句柄)存放在栈上,对象存放在堆上。

引用是一个(16进制的数字)地址指向了堆中的实际对象。可以有多个引用同时指向一个对象,其中任意一个引用修改了对象,由于其它引用指向的是同一个对象,导致所有引用指向的对象都发生变化。

然而,基本类型的变量(例如, int i = 0, j = 0;)修改i的值,不会影响到j的值。因为它们是分配在栈上,彼此独立的。

=========================割=========================

栈到底是什么呢?

栈是一块内存,它有一个指针指向当前执行地址,指针的上移下移就对应着内存的释放和分配。

栈英文对应Stack,它的意思是“成叠的放置”,也就是把一个一个的物体摞起来,一个压一个。它强调的是一个摞起来的状态。由于Stack在翻译的时候也有翻译成“堆栈”的,因此,栈 == 堆栈,在这叫堆栈只是想特别说明,这个栈是和堆相应的,还有方法栈、本地方法栈等。

在数据结构中,对应一种先入后出(FILO)的结构,区别于Queue的(FIFO)。Stack在方法调用的时候用的最多,每个方法的调用都是将方法对象(学名叫栈帧,把方法摘要从方法区取出,以及一些变量,程序指针都算上)入栈(也叫压入,press in),如果此方法还调用了其它的方法的话,就会重复上一步 操作把调用的方法继续入栈,循环此步骤,直到调用链结束。执行的时候按FILO的方式,从栈顶开始执行栈中的每一个方法对象,执行结束后出栈(也叫弹出,pop),直到栈底最后一个被执行完成,至此栈被清空(在程序异常之后,打印的异常日志就是栈中方法调用的顺序,可以看出它是反的,程序入口反而在最下面)。因此,Java中经常说到的一个词叫“方法栈”,其实就是当前执行方法的对象(包括一些变量,上下文信息,程序计数器等内容)。需要知道,在栈中指针向上移动,释放掉内存则意味着刚刚执行完的方法已经无迹可寻了,存的变量、对象也找不到了,多个方法之间互相调用就不能依靠栈内存来达到内存共享。(在C++中创建对象就可以通过是否使用new关键字,手动指定创建在stack还是heap上)。

堆?还是堆?

一直有一个疑问,Java内存中的heap和数据结构中的heap有没有关系?只知道堆是一个很大的内存空间,用来存放对象,是GC的亲密伙伴。但是它和数据结构中的堆(priority queue)是什么个关系,一直不清楚。

基于heap的优先队列,数据结构中的堆实现了这一策略,在加入新对象时可以任意加入,但是在取出时总是最小或最大的那一个(最大还是最小,取决于创建的是最大堆还是最小堆),是队列结构必须先进先出的一个补充。例如,医院挂号挂到100号了,来了一个快吹灯拔蜡的,你总不能让他等到101号吧,不然等他到号了,都快出了头七了。

在Java分配对象时,也说是分配到了堆上,此堆非彼堆,不是上面的数据结构上的堆了。

Stack Overflow上有人回答了这一问题:https://stackoverflow.com/questions/1699057/why-are-two-different-concepts-both-called-heap

Java 栈 顺序_Java 栈 顺序

参考上面的回答,得出一个结论:毛关系没有!就是有个沙雕把“heap”这个词推广了,然后被广大国人翻译成堆再继续推广,到了我们眼里,分配对象的位置就叫堆了。上面有一个人说的挺好,叫“池”可能更贴切。 

总结

在Java、Python等有垃圾回收机制的语言中,使用heap是很方便的,因为不需要考虑内存泄露的问题,典型的有人生没人养的情况,在创建出对象之后用一用就不管了,也不需要担心这个对象会不会去溜门撬锁,违法乱纪,影响社会秩序(内存泄露)。但是在C++中,如果使用了new 关键字创建了对象,在使用之后就得想着把它delete掉,不然是没人帮你干掉他的,如果量大了肯定会造成内存泄露。栈的使用就比较方便了,只需要移动指针就会分派、释放内存。

补充

方法区(method area)这个名字也很容让人产生误解,听上去好像是存放方法的区域一样,其实不然。它主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)。线程需要创建对象时,也需要从这里获取信息,存放的都是在整个程序中永远唯一的元素,因此它也是线程共享的。另外,在类加载的过程中就是把类信息加载到了方法区中(有点类似于进程准备好了所有的资源,待启动线程去重复使用)。

注:JDK 6及以前,String等字符串常量的信息是置于方法区中的,但是到了JDK 7,已经移动到了Java堆。