java的内存储器分配机制

java的内存分配机制

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 

静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他 们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不 允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 

栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈 式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运 行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构 所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。 静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有 的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的 内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序 分配和释放. 2.2 堆和栈的比较 上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态 存储分配,集中比较堆和栈: 从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主 要是由于堆和栈的特点决定的: 在编程中,例如 C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分 配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt) 一样,Stack Pointer 会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候, 修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分 配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然 分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的, 不是在运行时. 堆是应用程序在运行的时候请求操作系统分配给自己内存, 由于从操作系统管理的内存分配,所以在分配和 销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储 空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实 上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象 之后才能确定.在 C++中,要求创建一个对象时,只需用 new 命令编制相关的代码即可。执行这些代码时, 会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时 会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是 人的缺点,人的缺点往往也是人的优点(晕~). 2.3 JVM 中的堆和栈 JVM 是基于堆栈的虚拟机.JVM 为每个新创建的线程都分配一个堆栈.也就是说,对于一个 Java 程序来说, 它 的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM 对堆栈只进行两种操作:以 帧为单位的压栈和出栈操作。 我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前 帧。当线程激活一个 Java 方法,JVM 就会在线程的 Java 堆栈里新压入一个帧。这个帧自然成为了当前帧. 在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理 中的活动纪录的概念是差不多的. 从 Java 的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程 (在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。 每一个 Java 应用都唯一对应一个 JVM 实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有 类实例或数组都放在这个堆中,并由应用所有的线程共享.跟 C/C++不同,Java 中分配堆内存是自动初始化 的。Java 中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在 建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只 是一个指向这个堆对象的指针(引用)而已。 2.4 GC 的思考 Java 为什么慢?JVM 的存在当然是一个原因,但有人说,在 Java 中,除了简单类型(int,char 等)的数据结构, 其它都是在堆中分配内存(所以说 Java 的一切都是对象),这也是程序慢的原因之一。 我的想法是(应该说代表 TIJ 的观点),如果没有 Garbage Collector(GC),上面的说法就是成立的.堆不象栈 是连续的空间,没有办法指望堆本身的内存分配能够象堆栈一样拥有传送带般的速度,因为,谁会为你整理 庞大的堆空间,让你几乎没有延迟的从堆中获取新的空间呢? 这个时候,GC 站出来解决问题.我们都知道 GC 用来清除内存垃圾,为堆腾出空间供程序使用,但 GC 同时也担 负了另外一个重要的任务,就是要让 Java 中堆的内存分配和其他语言中堆栈的内存分配一样快,因为速度 的问题几乎是众口一词的对 Java 的诟病.要达到这样的目的,就必须使堆的分配也能够做到象传送带一样, 不用自己操心去找空闲空间.这样,GC 除了负责清除 Garbage 外,还要负责整理堆中的对象,把它们转移到一 个远离 Garbage 的纯净空间中无间隔的排列起来,就象堆栈中一样紧凑,这样 Heap Pointer 就可以方便的指 向传送带的起始位置,或者说一个未使用的空间,为下一个需要分配内存的对象"指引方向".因此可以这样 说,垃圾收集影响了对象的创建速度,听起来很怪,对不对? 那 GC 怎样在堆中找到所有存活的对象呢?前面说了,在建立一个对象时, 在堆中分配实际建立这个对象的内 存,而在堆栈中分配一个指向这个堆对象的指针(引用),那么只要在堆栈(也有可能在静态存储区)找到这个 引用,就可以跟踪到所有存活的对象.找到之后,GC 将它们从一个堆的块中移到另外一个堆的块中,并将它们 一个挨一个的排列起来,就象我们上面说的那样,模拟出了一个栈的结构,但又不是先进后出的分配,而是可 以任意分配的,在速度可以保证的情况下, Isn't it great? 但是, 列宁同志说了,人的优点往往也是人的缺点,人的缺点往往也是人的优点(再晕~~).GC()的运行要占用 一个线程,这本身就是一个降低程序运行性能的缺陷,更何况这个线程还要在堆中把内存翻来覆去的折腾. 不仅如此,如上面所说,堆中存活的对象被搬移了位置,那么所有对这些对象的引用都要重新赋值.这些开销 都会导致性能的降低. 此消彼长,GC()的优点带来的效益是否盖过了它的缺点导致的损失,我也没有太多的体会,Bruce Eckel 是 Java 的支持者,王婆卖瓜,话不能全信.个人总的感觉是,Java 还是很慢,它的发展还需要时间.

<!--EndFragment-->