按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
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还是很慢,它的发展还需要时间.
上面的体会是我看了TIJ.3rdEdition.Revision4.0中第四章之后得出的,内容和前面的有些不同.我没有看过侯捷的中文版本,但我觉得,在关键问题上,原版的TIJ的确更值得一读.所以和中文版配合起来学习是比较不错的选择.
我只能算一个Java的初学者,没想到起了这么个题目,却受到这么多人的关注,欣喜之余,也决心尽力写好下面的每一篇.不过这一篇完了,我就该准备赴美签证了,如果成功,那就要等到8月27号CS的研究生院开学之后,才有时间会开始研究下一章了,希望可以多从原版中获取一点经验.
分享到:
相关推荐
综上所述,JAVA中的内存分配策略包括静态存储分配、栈式存储分配和堆式存储分配。每种策略都有其适用场景和限制条件。JVM采用了基于堆栈的设计来管理和优化内存使用,同时通过垃圾回收机制自动管理对象的生命周期,...
Java内存分配策略包括静态存储分配、栈式存储分配和堆式存储分配。静态存储分配在编译时就确定了变量的内存需求,栈式分配在运行时按需分配,而堆式分配则处理编译时和运行时都无法确定存储需求的数据结构,如可变...
程序运行时内存分配是计算机科学中的重要概念,它关乎到程序的执行效率和资源管理。在深入了解这个主题之前,...在编写程序时,合理使用内存分配策略可以优化程序的资源消耗,提高运行效率,同时避免内存相关的问题。
Java的内存分配策略主要包括静态分配、栈式分配和堆式分配。其中,栈式分配和堆式分配是运行时动态进行的,具体如下: 1. **栈式存储分配**:栈式存储适用于已知大小的局部变量和方法参数。当一个方法被调用时,JVM...
在C编程语言中,内存管理是一项关键任务,它涉及到如何为程序中的变量和对象分配内存以及何时释放这些内存。理解C内存分配的五种...在实际编程中,应根据需求合理选择内存分配策略,以实现最优的资源利用和程序效率。
Java中的内存分配策略有三种:静态存储分配、栈式存储分配和堆式存储分配。静态存储分配是在编译时就能确定每个数据目标在运行时刻的存储空间需求的。栈式存储分配是由一个类似于堆栈的运行栈来实现的,程序对数据区...
《目标程序运行时的存储组织》是对程序运行过程中内存管理的深入探讨,主要涉及数据空间的三种使用和管理方法:静态存储分配、栈式动态存储分配和堆式动态存储分配。 首先,数据空间的管理是编程语言设计的重要部分...
本讲主要讨论两种存储分配策略:静态存储分配和动态存储分配,以及栈式和堆式存储分配。 静态存储分配是在编译阶段就确定了数据对象的存储位置,适用于那些在编译时就能确定大小的数据。例如,全局变量和常量通常在...
Java程序在运行时的内存分配是一个关键的概念,它关乎到程序的性能、稳定性和资源管理。这个主题主要涉及以下几个方面: 1. **JVM内存结构**:Java虚拟机(JVM)将内存划分为几个区域,包括堆(Heap)、栈(Stack)...
在这个实验中,我们将聚焦于内存管理的基本概念,特别是内存分配策略,以及如何通过编程语言(如VC++)来模拟这些过程。 内存管理是操作系统的核心功能之一,它涉及内存的分配、回收、地址映射和保护等多个方面。...
这包括栈分配(用于函数调用时的局部变量)和堆分配(用于程序自定义的内存需求)。堆分配通常由系统提供的库函数(如C语言的malloc和free)来实现。 内存回收是指当进程不再需要内存时,操作系统将其返回到可用...
《编译原理第8章 程序运行时的存储组织》主要探讨了程序在运行过程中内存的管理和分配策略。...理解栈和堆的区别,以及如何有效地使用它们来优化程序的性能和内存管理,是编程实践中不可或缺的知识点。
在计算机科学领域,内存管理是操作系统的关键组成部分,尤其是在Windows操作系统中。Windows内存管理机制负责有效地分配、使用和回收系统资源,...在实践中,结合使用各种内存分配策略,可以更好地满足不同场景的需求。
1. **内存限制**:由于嵌入式设备通常内存较小,合理分配栈和堆的大小至关重要。过大可能导致系统崩溃,过小可能限制程序功能。 2. **内存碎片**:堆内存的频繁申请和释放可能导致内存碎片,降低系统性能。为了优化...
在嵌入式系统开发中,内存分配器是...理解内存管理的基本原理,结合嵌入式环境的特性,采用合适的内存分配策略,是提高系统性能和稳定性的重要手段。在实际开发中,我们需要不断探索和优化,以适应各种复杂场景的需求。
存储分配策略主要分为三种:静态存储分配、栈式动态存储分配和堆式动态存储分配。静态存储分配在编译时就为数据对象分配固定的位置,适用于不允许递归或动态数据结构的语言,如FORTRAN。栈式动态存储分配主要用于...
在内存分配策略上,静态存储分配通常在编译时完成,适合静态数据结构;栈式存储分配在函数调用时进行,适用于局部变量和固定大小的数据结构;而堆式存储分配则在运行时进行,适合不确定大小或需要长期存在的数据。 ...
内存管理是计算机科学中的核心概念,它涉及到操作系统如何有效地分配和回收内存资源,以供程序运行。内存分配是其中的关键部分,确保程序可以正确地获取和释放存储空间。本资料集合详细介绍了内存管理、内存分配以及...
本章节主要探讨了三种基本的存储分配策略:静态存储分配、栈式存储分配和堆式存储分配。 1. **静态存储分配**:这是在编译阶段就为程序中的所有变量分配固定存储空间的方法。对于那些在编译时就能确定大小的数据...