黑马程序员技术交流社区

标题: 堆栈问题 [打印本页]

作者: 赵红萍    时间: 2012-10-28 10:56
标题: 堆栈问题
在java中创建一个数组,例如:String [] s = {"abc","efg"};那么s 存放在栈内存中,而实际的数组元素放在堆内存中,我想问,既然都是内存中,为什么要分堆内存和栈内存呢?这是java虚拟机分的吗?
作者: 林志进    时间: 2012-10-28 11:45
一、java虚拟机栈(java virtual machine stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。     
        经常有人把java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,java内存区域的划分其实远比这复杂。这种流行方式的划分只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“栈”就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
        局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
        其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
        在java虚拟机规范中,对这个区域规定了2种异常情况:如果线程请求的栈深度大与虚拟机所允许的深度,将抛出StackOverFlowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
二、        对于大多数应用来说,java堆(java Heap)是java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也逐渐变得不是那么“绝对”了。
        Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC”堆。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以java堆中还可以细分为:新生代和老生代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。如果从内存分配的角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。
        根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
作者: 李建强    时间: 2012-10-28 11:46
本帖最后由 李建强 于 2012-10-28 11:48 编辑

堆、栈其实是两种不同的数据结构。
栈很小,但速度快。
栈中主要存放函数参数值、局部变量值等基本数据类型和引用等,位置在一级缓存中,对于需要操作频繁的数据操作速度快。

堆事动态分配内存的,可以很大,但是慢
堆中主要存放new出的对象,位置在二级缓冲中,相对来说操作速度慢。


作者: 杨明宁    时间: 2012-10-28 12:06
堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由java虚拟机自动垃圾管理器来管理。在堆中产生了一个数组对象后,还可以在栈中定义一个特殊的变量,让栈中的这个变量取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象,引用变量就相当于是为数组或对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它时,才会变为垃圾,不能再被使用,但仍然占据内存空间不放,在随后一个不确定的时间被垃圾回收器收走。
作者: 李连闯    时间: 2012-10-28 12:46
本帖最后由 李连闯 于 2012-10-28 12:48 编辑

JVM依据电脑的实际物理内存可以对自己的栈内存和堆内存进行大小的分配,下面是主要的参数
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX: PermSize=64M JVM初始分配的非堆内存(这块儿的分号和P之间多加了个空格,要不然就变成表情了,:P,哈哈)
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配
至于分开堆栈内存的原因:需要知道栈是一段连续的内存空间,连续意味着快速的存取,适用于占用空间小的对象引用和基本数据类型的存储;
对于占用空间较大的对象来说,可能无法找到连续的存储空间来存放对象,所以需要把不连续的(与栈相区别)内存空间有序地组合到一起来存放对象,然后堆内存这个概念就出现了,堆内存是用来存占用空间大的对象的。希望有用。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2