当前位置:文档之家› Java_HotSpot虚拟机的内存管理

Java_HotSpot虚拟机的内存管理

Java_HotSpot虚拟机的内存管理
Java_HotSpot虚拟机的内存管理

Java HotSpot虚拟机的内存管理(j2se 5.0)

Sun Microsystems

April 2006

刘兵译

2011-10-20

https://www.doczj.com/doc/d610004678.html,/

liubingdian@https://www.doczj.com/doc/d610004678.html,

一.介绍

略……..

二. 手动VS 自动内存管理

略……..

三. 垃圾收集器概念

一个垃圾收集器的职责:

●分配内存

●确保所有应用的对象保留在内存中

●回收执行代码中任何不再可达的对象所占用的内存

存在引用的对象称之为live。不存在任何引用的对象称之为dead,术语上称为垃圾。查找并释放(也叫回收)这些对象占用空间的过程策划更能为垃圾回收

垃圾收集器可以解决大部分,但并非所有的内存分配问题。例如,你可以无限创建对象并且一直引用它们,直到无内存可用。垃圾收集是一项消耗时间、资源的复杂任务。

垃圾收集器使用精确的算法来组织分配和释放空间,对程序员而言,它是不可见的。堆内存空间通常是从一个大的内存池中分配的。

垃圾收集的时间取决于捡垃圾收集器本身。典型地,整体堆或堆的一部分,在内存暂用率达到一个百分比的阀值时会触发垃圾收集。

内存分配是一个艰难的任务,因为它包含在堆中查找一定大小的内存块。对于动态分配内存算法而言,最大的问题在于避免碎片的产生(见下文),同时要保持高效的分配和释放。

1.理想垃圾收集器的特点

一个垃圾收集器必须既安全又全面。也就是说,live数据千万不要被错误地释放,并且垃圾不应该停留超过一个收集周期而无法回收。

当应用程序不运行时,垃圾收集器能够高效执行并且不引起长暂停,这通常是合理的。然而,在大多数计算机相关的系统中,往往存在着时间、空间和频率之间取舍。例如:如果一个对内存很小,收集将会很快,但很快又会满了,因此需要更加频繁地收集。相反,大型堆将需要更长的时间填满,从而收集将不如以前频繁了,但它们可能需要更长的时间。

理想垃圾收集器的另外一个特点就是:限制碎片。当垃圾对象的内存被释放,自由空间可能会出现一小块一小块的,可能没有足够的空间在任何一个连续区域为一个大型对象的分配使用各个空间。消除碎片的方法之一就是压缩,不同垃圾收集器之间的设计选择会在下面讨论。

可扩展性也很重要。分配不应该成为一个多处理器系统上的多线程应用程序的可伸缩性瓶颈,并且收集也不应该是这样的瓶颈。

2.设计的选择

在设计或选择一个垃圾收集算法时需要确定很多内容:

●串行VS并行

使用串行收集,一次只会发生一件事件。例如,即使在多CPU下,只有一个CPU是用来执行回收的。当使用并行收集器,垃圾收集的任务是分割成几个部分的,各个字部分在不同CPU同时执行,并且可以更加快速完成收集,但会产生一些额外的复杂度和潜在的碎片。

●并发VS Stop-the-world

当使用stop-the-world垃圾收集,在收集的过程中,应用程序的执行完全暂停。另外一种情况下,应用程序的执行可以同时与一个或多个垃圾收集任务并行。通常情况下,并发垃圾收集器并发执行收集,但偶尔也会有短暂的stop-the-world暂停。Stop-the-world 垃圾收集器比并发收集器更加简单,因为在收集过程中,堆是被冻结的,对象不再变化。

它的缺点是,对于有些应用而言如此的暂停是不可取的。与此相对应的,当垃圾收集器是并行执行时,暂停时间会较短,但收集器必须格外小心,因为应用程序可能在同一时间更新对象。这会增加一些额外的开销,对性能有些影响,并且需要更多的堆内存。

●压缩VS 不压缩VS 拷贝

当垃圾收集器确定了内存中live对象和垃圾对象后,它可以通过移动所有的live对象并且完全回收剩下的内存来压缩内存。压缩后,第一次在一个空闲内存中分配对象时简单且快速的。可以利用一个简单的指针保持跟踪对象的下一个位置分配。和一个压缩收集器相比,非压缩收集器方式垃圾对象占用的内存,它并没有为了创建一个大的可回收区域而移动所有live对象,这样做的好处就是更快地完成垃圾收集,但缺点就是潜在的内存碎片。在一般情况下,它要比在压缩后的堆中分配对象要昂贵,它可能需要搜索整个堆以获得一个邻近的连续的足够大的内存来分配对象。第三种方法是复制收集器,复制活动对象至不同的内存区。这样做的好处是,source区可以被认为是空的,并且可以用于快速简单的后续分配,但是缺点就是复制所产生的额外的空间和时间花费。

3.性能指标

下面是垃圾收集器的一些性能指标,包含:

●吞吐量

●垃圾收集的开销

●暂停时间

●收集频率

●尺寸----大小的测量,例如堆内存大小

●速度---一个对象从变成垃圾到所暂用的内存回收并且可用所花费的时间。

一个交互式的应用程序可能需要低暂停的时间,而对于非交互式的应用程序更重要的是整体的执行时间。一个实时的应用程序要求垃圾回收的时间和任何时期收集器花费时间比例均要有个上限,小块内存会在小型个人电脑或嵌入式系统运行的程序上比较关注。

4.分代收集器

有一种称之为分代收集器的技术,就是将内存划分成几代,也就是说,将不同年龄段的对象划分在不同的池中。例如,一个广泛使用的配置有两个分代:Young区、Old区。

在不同的分代上使用不同的算法来执行垃圾收集,每种算法是基于不同代通常能观察到的特征来优化的。分代垃圾回收利用以下几点意见(关于多种编程语言,包括Java编程语言编写的应用程序被称为weak generational hypothesis):

●大部分分配的对象不会存活很久,那就是说它们die young

●从older到younger的对象很少存在

Young区收集发生比较频繁、高效、快速,因为Young区空间通常很小,可能包含很多不再被引用的对象。

Young区收集后存活的对象最终回到Old区。见图一,Old区的入住率增长速度会比Young 区慢。因此,Old区收集是不频繁的,但会采取更长的时间来完成。

Young区选择垃圾收集算法通常会对速度有很大的要求,因为Young区收集较为频繁。

另一方面,Old区通常是管理的一种算法,更加节省空间,因为Old区占用大部分堆,并且Old区收集算法必须在低密度垃圾收集下工作。

四. J2SE 5.0 HotSpot JVM垃圾收集器

在J2SE5.0 update 6版本中,Java HotSpot虚拟机包括四个垃圾收集器。所有的收集器都是分代的。本节介绍分代和收集类型,并讨论为什么对象分配往往是快速和高效的。然后,讲述每个收集器的详细西悉尼。

1.HotSpot分代

Java HotSpot虚拟机的内存分为三代:Young区、Old 区、Permanent区。大多数对象最初分配在Young区。Old区包含那些从Young区收集后存活下来的对象,以及一些可能直接分配在Old区的大对象。Permanent区持有JVM认为方便垃圾收集器管理的对象,如描述类和方法的对象、以及类和方法本身。

Young区包含Eden区和两个小的Survivor区,如图2所示。大多数对象最初分配在Eden 区(如前所述,一些大的对象可直接分配在Old区)。Survivor区容纳至少经过一次Young 区收集后存活下来的对象,并且这些对象被赋予了更多的机会被回收,要么就要被认为“old enough”而被晋升到Old区。在任何特定时间下,Survivor区的其中一个(在图中被标记为From)保存这样的对象,而另外一个Survivor区则是空的,直到下次收集时都保持未使用。

2.垃圾收集类型

当Young区被填满了,Young区收集(也被称为minor收集)就会触发。当Old区或Permanent区被填满了,一个被称为Full收集(也被称为major收集)将会被触发。也就是说,所有分代的收集,在一般情况下,Young区是首先被收集的(使用Young区的收集算法,因为这个算法是Young区垃圾收集最有效的算法)。Old区的收集算法是回在Old区和Permanent区收集。如果采用压缩的话,那每代分别压缩。

有时Old区太满以至于无法接受所有的对象(这些对象是首先经过Young区的一次收集,并被提拔到Old区的)。在这种情况下,除了CMS收集器外的其他收集器,Young区的收集算法将无法执行。相反,Old区收集算法将会对整个堆进行收集。(CMS Old区算法是一个特例,因为它不能针对Young区进行收集)

3.快速分配

正如您将在下面描述垃圾收集器的说明看出,在许多情况下有足够大的联系内存块来分配对象。从这些块中分配内存是很有效的,使用了一个简单的bump-the-pointer技术。

也就是说,在分配对象结束以前始终保持跟踪。当一个新的分配请求得到满足,所有需要做的是坚持对象是否适合在余下的分代,如果是的话,更新指针并初始化对象。

对于多线程应用程序,分配操作需要是多线程安全的。如果使用全局锁来确保这点的话,在分代的内存分配会成为一个瓶颈并降低性能。HotSpot JVM是通过称为线程本地分配缓冲器(TLABs)的一种技术。这通过每个线程有自己的缓冲区(即一小部分一个分代)来实现多线程的内存分配。由于只有一个线程可以分配到单独的TLAB,分配可以在无需任何锁定的情况下,快速利用bump-the-pointer技术来实现。只有偶尔,当一个线程填满了它的TLABs,则必须利用同步来获得一个新的TLAB。由于使用了TLABs使得可以最大限度地减少空间浪费。例如:收集器分配的TLABs平均浪费的空间不到Eden区的1%。TLABs 线程分配和bump-the-pointer技术的结合使得内存分配只要大约10个本地指令。

4.串行收集器

使用串行收集器,Young区、Old区垃圾收集都是stop-the-world并且串行执行的(使用一个CPU)。就是说,当垃圾收集器触发的时候应用程序的执行会被暂停。

在Young区使用串行收集器

图3展示了Young区使用串行收集器的运作。在Eden区的存活对象(除了那些太大

而无法放在To区的对象)被复制到最初是空的Survivor区(图中标记为To)。太大的

对象直接被复制到Old区,存活在已被使用的Survivor区(标记为From)中,相对较

年轻的对象被复制到To区,但那些已经足够老的对象则被复制到Old区。如果To

空间满了,不管经历多少次Young去回收后还存活的对象,还是那些存活在Eden

区或还没有被复制到To区的对象都将变为Old对象。顾名思义,那些存活在Eden

区或From区的被复制的对象不是live的,它们并不需要进行审查。(图中X标记的

垃圾对象,垃圾收集器并不会检查或标记这些对象)

在Young去的收集结束后,Eden区和之前被占用的Survivor区都是空的,只有原来为空的Survivor区包含活动对象。此时,Survivor区互换角色。见图4

在Old区使用串行收集器

使用串行收集器,Old区和Permanent区使用mark-sweep-compact算法收集。在mark阶段收集器确定那些对象仍然是live的,sweep阶段识别出各个分代的垃圾对象,compact阶段收集器执行压缩,将live对象压缩到Old区(对Permanent区也是一样) 的开始位置,在另外一端留下一个连续的自由空间。见图5,压缩使用快速、bump-the-pointer技术允许以后再Old区和Permanent区的内存分配。

●什么时候选择串行收集器

串行收集器是大多数客户端式的机器上运行的应用程序并且没有要求低暂停时间

要求的首选。在现如今的硬件上,串行收集器有效地管理了很多重要的应用程序,这些应用程序有64M堆内存和相对较短的最坏的暂停时间(小于全局收集时间的一

半)

●串行收集器的选择

在J2SE5.0发行版,串行收集器会自动作为非服务器级别的机器的默认垃圾收集器。

在其他机器上,可以使用-XX:+UseSerialGC命令来强制使用串行收集器。

5.并行收集器

现如今,许多Java应用运行在大量物理内存和多CPU的机器上。并行收集器也被称为吞吐量收集器,它被开发出来用来使用可用的CPU的资源,而不是让它们空闲,导致只有一个垃圾收集器工作。

●在Young区使用并行收集器

在Young区上,并行收集器使用串行收集器同样算法的并行版本。他仍然使用

stop-the-world and coping算法,但是使用多CPU并行执行Young区的回收,减少了

垃圾收集的开销,从而提高了应用程序的吞吐量。图6说明了串行收集器和并行收

集器在Young区收集的差异。

●在Old区使用并行收集器

在Old区,并行收集器使用了和串行收集器同样的串行mark-sweep-compact收集算法。

●何时使用并行收集器

应用可以受益于并行收集器,这些收集器运行在多CPU的机器上并且没有暂停时间限制。Old区垃圾收集仍然会发生很长时间,但是这种情况非常的罕见。并行收集器通常适用于做批处理、计费、薪资、科学计算等的应用程序。

您可能会想要考虑并行压缩收集器(接下来会介绍)取代并行收集器,因为前者可以在所有区域执行并行收集,而不仅仅是Young区。

●并行收集器的选择

在J2SE5.0发行版中,在服务器级别的机器上并行收集器是作为默认的垃圾收集器。

在其他机器上面可以通过-XX:+UseParallecGC命令行来执行。

6.并行压缩收集器

并行压缩收集器是在J2SE5.0 update 6版本中介绍的。它不同于并行收集器,在于它对Old区的垃圾收集使用了一个新算法。

备注:最终,并行压缩收集器会替代并行收集器。

●在Young区使用并行压缩收集器

在Young区,并行压缩收集器与并行收集器同样的算法。

●在Old区使用并行压缩收集器

使用并行压缩收集器,Old区和Permanent区使用stop-the-world方式收集,采用并行滑动压缩。收集器采用三个阶段,首先每个分代从逻辑上被划分为固定大小的区

域。在marking阶段,从应用程序代码能否直接获得的对象(root可及)是在垃圾收

集线程中分配的,并行所有的存活对象都是并行被标记的。Summary阶段的工作是

在区域中,而不是对象上。由于之前收集过程中的压缩,通常每个分代的左侧部分

密集,因为这里包含大多数的live对象。在如此密集的区域,通过压缩来回收空间

是不值的。因此,summary阶段的第一件事情就是从最左边开始检查区域的密度,

直到达到一个可以值得回收的区域。这个区域的左侧被认为是密集的区域,并不会

移动在此区域的对象。此区域的右侧将会被压缩,消除所有的无用空间。Summary

阶段为没一个压缩区域计算和存储第一个字节的实时数据。

备注:summary目前是串行化的,并行化是有可能的。

在compaction阶段,垃圾回收线程使用汇总数据来填充特定空间,并且线程可以

独立复制数据到该区域。这将会导致堆的一端数据很密集,另外一端有一个大的空

区域。

●何时使用并行压缩收集器

和并行收集器一样,并行压缩收集器也适合跑在多CPU机器上的应用程序。另外,

对于有暂停是假限制的应用上面,在Old区并行收集减少暂停时间使得并行压缩收

集器更加适合。并行压缩收集器可能不适合大型共享机上面的应用程序(如SunRays),其实没有一个单一的应用程序可以垄断多个CPU长时间。在这样的机器上,可以考

虑减少用户垃圾收集的线程(通过-XX:ParallelGCThreads=n命令行选项),或者选择其

他的垃圾收集器。

●并行压缩收集器的选择

如果你想使用并行压缩收集器,你必须使用-XX:+UseParallelOldGC命令行选项。

7.并发标记清除(CMS)收集器

对于许多应用而言,端到端的吞吐量并不如快速响应那么重要。Young区收集通常不会导致长期停顿。然而Old区收集虽然并不常见,但也会导致长暂停,尤其涉及大型堆时。

为了解决这个问题,HotSpot JVM包括一个称之为并发标记清楚(CMS)的收集器,也称之为低延迟收集器。

●在Young区使用CMS收集器

在Young区,CMS收集器与并行收集器采用同样的方式。

●在Old区使用CMS收集器

在绝大多数情况下,使用CMS收集器回收Old区垃圾是和应用程序并行执行的。

CMS收集器的一个回收周期是以一个短暂的停顿开始的,这称为initial mark(标记

root可达对象)。然后在concurrent marking phase(并发标记阶段),收集器标记所有

root可及的live对象。由于在标记暂停发生的是和,应用程序本身正在运行并更新

引用字段,并不是所有的live状态的对象都保证在并发标记结束时被标记。为了处

理这个问题,应用程序第二次短暂停,称之为remark,在并发标记阶段重新定位任

何修改的对象。因为这次remark暂停会比initial mark长,所以为了提高效率,这

个是多线程并行运行的。

在remark标记的最后阶段,在堆中所有的live对象确保已被标记,所以随后的并发扫描阶段回收以确定的垃圾,图7 说明了在Old区,使用mark-sweep-compact收集器和CMS是收集器之间的差异。

在remark暂停阶段的一些任务,比如revisiting对象,会增加收集器的工作量。对所有收集器而言这是一个典型的,为了减少暂停时间的权衡策略。

CMS收集器是唯一一个不进行压缩的收集器。也就是说,当它释放dead对象所暂用的内存后,并没有移动live对象至Old区的末尾,见图8。

这样可以节省时间,但由于自由空间是不连续的,收集器不能使用简单的指针,指向下一个对象的可分配地址。相反,它现在需要使用自由列表,也就是说,它会创建一些连接在一起的未分配的内存名单以及每次需要分配的一个对象,相应的列表(更加所需的内存量)必须足够大,以确保所有的对象都能够存放并且可搜索。因此,分配到Old区比使用简单的bump-the-pointer技术更加昂贵。这也影响了在Young

区收集时,在Old区分配内存的开销

CMS收集器的另外一个缺点就是比其他收集器需要更多的堆内存。由于该应用程序被允许在运行时标记,它可以继续分配内存,从而有可能继续增长Old区的内存占用。此外,虽然收集器确保在mark阶段live对象不会被回收,但是在mark阶段会产生新的垃圾对象(这些对象会在下次Old区回收时被回收掉),这些对象被策划更能为floating垃圾。

最后,由于缺少压缩,碎片可能会产生。为了处理碎片,CMS收集器追踪popular 对象的大小、估计未来的需求并可能分裂或加入空闲块以满足需求。

不像其他收集器,CMS收集器并不会在Old区满时启动Old区收集。相反地,它会试图在还未满之前就开始收集。否则,CMS收集器将会使用串行和并行收集器使用stop-the-world、mark-sweep-compact算法来进行收集。为了避免这种情况,CMS 收集器启动一个基于前面Old区收集时间和速度的统计结果来避免这样的情况。如果Old区的暂用率超过了initiating暂用率,将会触发Old区收集。Initiating暂用率的值可以通过命令行选项-XX:CMSInitiatingOccupancyFraction=n来设置,其中n是占Old区的百分比大小,默认值是68。

综上所述,与并行收集器相比,CMS收集器减少Old区的暂停时间,有时可能会非常牺牲Young区的暂停时间,减少一些吞吐量以及需要额外的堆内存的要求。

●增量模式

CMS收集器可以被用来在并发阶段逐步完成。这种模式是为了减轻长期并行阶段的影响,通过定期停止并发让步于应用程序。在Young区收集之间,收集器所做的工作被分成多个时间段。当应用需要低暂停时,运行在多处理器机器上的并行收集器是非常有效的。使用这种模式的更多信息,请参考第九章“Java虚拟机5.0版本垃圾回收调优”。

●何时选择CMS收集器

如果你的应用需要更短的垃圾收集暂停,并能够负担得起,应用程序运行时与垃圾收集器共享处理器资源,那么就可以使用CMS收集器(由于其并发性,在一个垃圾收集周期内CMS收集器会占用CPU周期)。通常情况下,一个比较大的长寿命的数据(一个大的Old区),并且在两个或多个处理器的机器上运行的应用程序,往往得益于此收集的使用。一个例子就是Web服务器。CMS收集器应考虑用作需要低暂停时间的应用环境下。它也可能在一个适度规模的单一处理器上的Old区交互式应用程序上取得良好的效果。

●CMS收集器的选择

如果你想使用CMS是收集器,你必须使用-XX:+UseConcMarkSweepGC的命令行选项。

如果你想在增量模式下运行,也可以通过-XX:+CMSIncrementalMode的命令行选项来启用。

五. 工效学—自动选择和性能调优

在J2SE 5.0发行版本中,垃圾收集器的默认堆内存大小、选择client或server模式自动取决于应用程序运行的平台和操作系统。这些自动选择可以更好地满足不同类型的应用需求,同时比以前的版本需要更少的命令项。

此外,一个新的动态调整收集方式已经添加到并行垃圾收集器。通过这种方式,用户指定所需的行为,并且垃圾收集器可以动态调整堆内存的大小,以达到要求的行为。依赖于平台的默认选择和垃圾收集调整,使用所需的行为的组合简称为工效。工效学的目标是以最小的命令行调优来达到好的性能。

1.自动选择收集器、堆内存已经虚拟机

服务器级别的机器要求:

1. 2个以上的处理器

2. 2G以上物理内存

服务器级别的JVM适用于所有平台,除了令人讨厌的32位Windows操作系统平台。

译者注:哈哈,这个真搞笑,原来Windows操作系统Sun(Oracle)工程师也不喜欢。

在非服务器级别的机器上,JVM默认的垃圾收集器、堆内存大小是:

1.The client JVM

2.串行垃圾收集器

3.初始化堆内存大小为4M

4.最大堆内存大小为64M

在服务器级别的机器上,JVM总是server级别的,除非你明确指定使用-client命令行选项。在服务器级别的机器上默认的垃圾收集器是并行垃圾收集器。非服务器级别是串行收集器。

在服务器级别的机器上运行的并行垃圾收集器JVM(不管是client还是server),默认的初始堆大小如下:

1.初始堆内存为实际物理内存的1/64(备注:最小堆内存为32M,服务器级别机

器至少2G内存,2G*1/64是32M)

2.最大堆内存大小是物理内存的1/4,最大可达到1G(32位机器而言)

否则,使用相同的默认大小的非服务器级别机器(4M初始化堆大小和64M最大堆大小)默认值总是可以通过命令行选项覆盖。相关的选项在第八章介绍。

2. 基于行为的并行收集器调优

在J2SE 5.0 发行版中,一个新的调优方法已经被添加到并行垃圾收集器中,是基于应用程序的行为。命令行选项用于指定最大暂停时间、应用程序的吞吐量目标方面的行为。

●最大暂停时间目标

最大暂停时间目标可以使用下面的命令行选项:

-XX:MaxGCPauseMillis=n

这被解释为提示并行收集器n毫秒或更少的暂停时间。并行收集器会调整堆的大小

和其他垃圾收集相关的参数来试图保持收集暂停小于n毫秒。这些调整可能会导致

垃圾收集器以减少应用程序的总体吞吐量来达到目标,可能某些情况下所需的暂停

时间目标不能得到满足。

醉倒暂停时间的目标是会应用到每个分代的。通常情况下,如果目标没有达到,每

代的大小会变小以达到目标。最大暂停时间默认是不设置的。

●吞吐量目标

吞吐量目标是根据垃圾收集的时间和花在垃圾收集外的时间(被称为申请时间)来

测试的。可以通过下面的命令行选项指定:

-XX:GCTimeRatio=n

垃圾收集、申请时间的比例是:

1 / (1 + n)

例如:-XX:GCTimeRatio=19设置垃圾收集申请的时间为总时间的5%。默认的目标

为1%(即n=99)。在垃圾收集器上花费的时间实在所有分代上收集的总时间。如果

吞吐量的目标没有达到,分代的大小都会努力增加,这样做是为了提高垃圾收集时

应用程序的运行时间。一个更大的分代会花费更多的时间来填补。

●Footprint目标

如果吞吐量和最大暂停时间目标已经达到,垃圾收集器会减少堆内存的大小,直到

其中一个目标(一般是吞吐量目标)不能达到。目标不能得到满足,然后就解决。

●目标优先级

并行垃圾收集器尝试满足最大暂停时间,当这个目标达到时才会考虑吞吐量的目标。

同样的,Footprint目标只考虑两个目标都已经达到。

六.最佳实践

在上一节工效学描述的:自动垃圾收集器、虚拟机以及堆内存大小是大量应用调优的合理选择。因此,最初的建议是不要选择或配置垃圾收集器。也就是说,让虚拟机基于系统平台和操作系统自动选择垃圾收集器。然后测试你的应用,如果性能可以接受,具有高吞吐量和低暂停时间,那就OK。你并不需要故障排除或修改垃圾收集器的选项。

另一方面,如果你的引用看起来有关于垃圾收集性能方面的问题,你第一件需要考虑的事情就是基于你的应用和平台特性而选择的默认垃圾收集器是否合适。如果不合适,选择另外一个你认为合适的收集器,并且测试它的性能是否可以接受。

你可以使用在第七章描述的工具来测试和分析性能指标。基于测试结果,你可以考虑修改选项,比如那些控制堆大小或垃圾收集行为。一些最擦汗工农业的参数选项在第八章已经描述了。请注意:最好的性能调优就是先测试,然后再调优。有关如何使用测试来测量你的代码,将会实际上被使用。另外,提防过渡优化,因为应用程序的数据集、硬件、甚至垃圾收集器的实现都有可能随着时间而改变。

本章提供了有关选择垃圾收集器以及指定堆大小的信息。然后提供了并行垃圾收集器调优的一些建议,并且给出OutOfMemoryErrors问题的分析思路。

1. 何时选择一个不同的垃圾收集器

各种垃圾收集器的命令行选项如下:

-XX:+UseSerialGC

-XX:+UseParallelGC

-XX:+UseParalledOldGC

-XX:+UseConcMarkSweepGC

2. 堆内存大小

第五章讲诉了默认堆初始和最大内存大小。这些参数大小对于大部分的应用都是Ok的,但是如果你碰到分析性能问题以及OutOfMemoryError问题,你可以通过命令行修改在第八章中讲诉的参数选项。例如,在非server级别的机器上面,默认的堆内存大小是64M,这通常都太小了。所以你可以使用-Xmx选项来指定一个更大的堆大小。尽量获取更多的堆内存,除非你有长暂停时间的问题。吞吐量与可以内存是成正比的。拥有足够的可用内存是最重要的影响垃圾收集性能的因素。

在确定了总的可用堆内存之后,你应该考虑各个分区的内存大小。第二个最影响垃圾收集性能的因素就是Young区的内存大小。尽量给予Young区更多的内存大小,除非你找到Old区收集的问题或暂停时间的无奈提。然后,当您使用串行收集器时,不应该分配超过总内存大小一半的内存给Young区。

当你使用并行垃圾收集器之一时,最好事指定所需的行为而并非确切的堆内存大小,正如上面所说,让收集器自动和动态地修改堆内存大小,以达到这种行为。

3. 并行收集器的调优策略

如果垃圾收集器(自动或明确指定)选择了并行收集器或并行压缩收集器,然后为应用指定吞吐量的目标。不要选择对内存的最大值,除非你需要一个比默认值更大的堆内存。

为了达到所需的吞吐量,对内存大小将会增长或收缩到一定大小。在初始化过程中堆大小和改变应用程序的行为是可以预期的。

如果堆内存增长到最大值,在绝大多数情况下,这意味着在此情况下吞吐量的目标没有达到。设置堆内存的最大值接近系统的最大物理内存,但是并不到导致应用的swapping。

再次执行应用程序,如果吞吐量仍然没有达到,那么就增加物理内存吧。

如果吞吐量的没标达到了,但是暂停时间太长了,那就选择一个最大暂停时间目标。选择一个最大暂停时间可能意味着您的吞吐量目标无法实现了,所以选择一个应用可以接受的折中值。

垃圾收集器为了满足竞争的目标,即使已经达到了稳定的状态,堆内存的大小也是在振荡的。吞吐量的目标(需要更大的堆内存)和最大暂停时间的目标以及最小footprint(两个都需要更小的堆内存)之间都有一定的不可调和的矛盾。

4. 如何应对内存溢出错误

内存溢出错误是许多开发人员都会碰到的一个问题。当有足够的空间分配一个对象时抛出这个错误,也就是说,垃圾收集并不能进一步腾出可用空间以适应新的对象,并且对内存不能进一步扩大。内存溢出错误并不一定意味着内存泄露。这个问题可能仅仅是一个配置问题,例如,如果指定的堆大小(或如果没有指定,即为默认大小)满足不了应用程序。

诊断一个内存溢出错误时,第一步是检查完整的错误消息,在异常消息中,紧跟在”https://www.doczj.com/doc/d610004678.html,ng.OutOfMemoryError”后的近一步的提示信息。这里有一些常见的例子,它可能意味着什么,它需要做些什么:

●Java heap space

这表明,一个对象不能在堆中分配。这个问题可能仅仅是一个配置问题。例如,如

果-Xmx的命令行选项(或默认被选中)指定的最大堆大小不能满足应用程序,那么你

会得到这个错误。它也有可能说明,不再需要的对象不能被垃圾收集,因为应用程

序时无意间对它们保持引用的。HAT工具(见第七章)可以用来查看所有可达的对象,并且了解它们之间的引用关系。此错误的另外一项潜在来源可能是应用程序过渡使

用了finalize,如该线程调用终结,无法跟上finalize队列终结的速度。JConsole的

管理工具可用于监视finalize的对象数量。

●PermGen Space

这表明Permanent区满了。如前所述,这里是JVM存储元数据的区域。如果一个

应用程序加载了大量的类,则可能需要增加Permanent区内存大小。为此,您可以

通过指定命令行选项-XX:MaxPermSize=n,其中n指定大小。

Requested array size exceeds VM limit

这表明应用程序试图分配一个比堆内存大的数组。例如,如果应用程序试图分配

512M阵列,但最大堆大小是256m,那么这个错误将会被抛出。在大多数情况下,

问题可能是堆大小太小或应用程序中的一个错误导致的。

第七章所述的一些工具可以用来诊断OutOfMemoryError错误的问题。其中最有用的堆分析工具(HAT),jconsole管理工具以及jmap工具的-histo选项。

七.垃圾回收性能评测工具

可以利用不同的诊断和检查工具来评估垃圾收集的性能。本节提供了其中的一些简要概述,欲了解更多信息,请参阅第九章“工具和故障排除”的链接。

1.-XX:+PrintGCDetails命令行选项

获取垃圾收集的初始信息最简单的方法之一是使用命令行选项-XX:+PrintGCDetails。对于每次收集的输出结果西悉尼,例如每个分代垃圾回收前后活动对象的数量、每个分代的可用空间大小以及每次垃圾收集器的时间长短。

2.-XX:+PrintGCTimeStamps命令行选项

如果命令行选项石永红-XX:+PringGCTimeStamps,就会输出除了以及输出的信息外,每次回收的开始时间戳。时间戳可以帮助您管理其他记录的时间与垃圾收集日志。

3.jmap

jmap是一个包括在Solaris、Linux(不包含windows)版本的JDK中的命令行使用工具。它打印出JVM或核心文件相关的内存统计数据。如果它使用未带任何参数,它将会打印共享加载的对象列表,类似Solaris pmap utility输出。为了获取更多的信息,使用-heap、-histo、-permstat选项。

-heap命令行选项是用来获取垃圾收集器的名称、算法细节(如并行垃圾收集器郑子昂使用的线程数)、堆内存配置信息以及堆内存使用情况摘要的相关信息。

-histo命令行选项可以用来获取一个class-wise堆直方图。对于每个类,它打印对中内存的数量,那些以字节为单位消耗的内存总量,和类的全限定名。当试图理解堆内存的使用情况时,使用直方图非常有效。

Permanent区的配置大小对那些动态生成和加载大量类的应用程序(例如jsp和web容器)也是很重要的。如果一个应用加载了太多的类,然后触发OutOfMemoryError。jmap的

-permstat选项可以用来获得Permanent区的对象统计信息。

4.jstat

jstat工具使用HotSpot JVM内置的instrumentation,来提供性能和资源消耗信息。该工具还可以用来诊断堆大小和垃圾收集有关的特定性能问题。它有许多选项,可以打印出各个分代上垃圾收集行为的能力和使用的统计数字。

5.HPROF: Heap Profiler

HPROF是JDK 5.0附带的一个简单的profiler代理。这是一个动态链接库,使用Java虚拟机工具接口(JVM TI)。它将分析的信息以ASCII或二进制各市地额数据写入到一个文件或一个socket流。这些信息可以使用profiler front-end工具进一步分析加工。

HPROF能够呈现CPU使用率,堆分配统计和contention profiles监测。此外,它可以输出完全的堆dump文件并报告所有的监控器和Java虚拟机的线程状态。当分析性能、锁争用、内存泄露以及其他问题时,HPROF是很有用的。HPROF文档链接请参数第九章。

译者注:这里讲的是jhat工具,可参考:

https://www.doczj.com/doc/d610004678.html,/javase/6/docs/technotes/tools/share/jhat.html

6.HAT:Heap Analysis Tool

HAT有助于调试unintentional object retention。这个术语是用来描述一个不再需要的但被其他live对象引用的对象。HAT提供了一个方便的途径来浏览使用HPROF堆快照生成的对象拓扑。该工具允许查询对象,包括”向我展示root对象到这个live对象的所有应用路径”这样的查询。HAT文档的链接,请参与第九章

八. 垃圾收集相关的命令行选项

一个垃圾收集器可以有许多的命令行选项,特别是堆或分代的大小,修改垃圾收集的行为以及获得垃圾收集的统计信息。这章将讲述最常用的命令选项。更多更详细的信息可以参考第九章。备注:数字的后面加上”m”或”M”是表示以兆字节为单位,”k”或”K”是以千字节为单位,”g”或”G”是以千兆字节为单位。

1.垃圾收集器的选择

选项垃圾收集器的选择

-XX:+UseSerialGC 串行

-XX:+UseParallelGC 并行

-XX:+UseParallelOldGC 并行压缩

-XX:+UseConcMarkSweepGC 并发标记清除(CMS)

2.垃圾收集器的统计

选项描述

-XX:+PrintGC 输出每次垃圾收集的基础信息

-XX:+PringGCDetails 输出每次垃圾收集的更加详细的信息

-XX:+PrintGCTimeStamps 输出每次垃圾收集时间触发时的时间戳。与

-XX:+PrintGC或-XX:+PrintGCDetails搭配使用。

3.堆和分代的大小

选项默认值描述

-Xms n见第五章初始堆内存大小

-Xmx n见第五章最大堆内存大小

-XX:MinHeapFreeRatio=minimum 与

-XX:MaxHeapFreeRatio=maximum 40(最小)

70(最大)

目标范围内可用空间占整堆的比例。这

些是应用在每个分代上的。例如:如果

最小值是30并且在有一个分代上可用

空间的比例低于30%,那么这个分代的

大小将会扩展,直到可用空间所占的比

例达到30%。同样,如果最大值是60

并且可用空间的比例大于60%,那么这

个分代的大小会回收,直到可用空间缩

回到60%.

-XX:NewSize=n依赖于平台默认Young区的初始大小

-XX:NewRatio=n client JVM 为2

server JVM为8 Young区和Old区的比例。例如:如果n是3,则比例就是1:3,就是说Young 区和Old区的比例就是1:3

-XX:+SurvivorRatio=n32 Survivor区和Eden区的比例。例如:如

果n是7,每个Survivor区占Young区

的1/9(不是1/8,因为有两个Survivor

区)

-XX:MaxPermSize=n依赖于平台Permanent区的最大内存

4.并行和并行压缩收集器的选项

选项默认值描述

-XX:ParallelGCThreads=n CPU个数垃圾收集器的线程数

-XX:MaxGCPauseMillis=n无默认值提示收集器暂停时间要<=n毫秒

-XX:GCTimeRatio=n99 设置垃圾回收的时间占整个时间的1/(1+n) 5.CMS收集器的选项

选项默认值描述

-XX:+CMSIncrementalMode 禁用启用并发阶段逐步完成的方式,定期停止并发阶

段的应用程序处理,即增量模式

-XX:+CMSIncrementalPacing 禁用根据应用程序的行为自动调整每次执行的垃圾

回收任务的幅度,与上面的参数配合使用。

-XX:ParallelGCThreads CPU个数并发垃圾回收线程数

九. 更多信息

略……..

操作系统内存管理复习过程

操作系统内存管理

操作系统内存管理 1. 内存管理方法 内存管理主要包括虚地址、地址变换、内存分配和回收、内存扩充、内存共享和保护等功能。 2. 连续分配存储管理方式 连续分配是指为一个用户程序分配连续的内存空间。连续分配有单一连续存储管理和分区式储管理两种方式。 2.1 单一连续存储管理 在这种管理方式中,内存被分为两个区域:系统区和用户区。应用程序装入到用户区,可使用用户区全部空间。其特点是,最简单,适用于单用户、单任务的操作系统。CP/M和 DOS 2.0以下就是采用此种方式。这种方式的最大优点就是易于管理。但也存在着一些问题和不足之处,例如对要求内

存空间少的程序,造成内存浪费;程序全部装入,使得很少使用的程序部分也占用—定数量的内存。 2.2 分区式存储管理 为了支持多道程序系统和分时系统,支持多个程序并发执行,引入了分区式存储管理。分区式存储管理是把内存分为一些大小相等或不等的分区,操作系统占用其中一个分区,其余的分区由应用程序使用,每个应用程序占用一个或几个分区。分区式存储管理虽然可以支持并发,但难以进行内存分区的共享。 分区式存储管理引人了两个新的问题:内碎片和外碎片。 内碎片是占用分区内未被利用的空间,外碎片是占用分区之间难以利用的空闲分区(通常是小空闲分区)。 为实现分区式存储管理,操作系统应维护的数据结构为分区表或分区链表。表中各表项一般包括每个分区的起始地址、大小及状态(是否已分配)。

分区式存储管理常采用的一项技术就是内存紧缩(compaction)。 2.2.1 固定分区(nxedpartitioning)。 固定式分区的特点是把内存划分为若干个固定大小的连续分区。分区大小可以相等:这种作法只适合于多个相同程序的并发执行(处理多个类型相同的对象)。分区大小也可以不等:有多个小分区、适量的中等分区以及少量的大分区。根据程序的大小,分配当前空闲的、适当大小的分区。 优点:易于实现,开销小。 缺点主要有两个:内碎片造成浪费;分区总数固定,限制了并发执行的程序数目。 2.2.2动态分区(dynamic partitioning)。 动态分区的特点是动态创建分区:在装入程序时按其初始要求分配,或在其执行过程中通过系统调用进行分配或改变分区大小。与固定分区相比较其优点是:没有内碎

Java虚拟机(JVM)参数配置说明

Java虚拟机(JVM)参数配置说明 在Java、J2EE大型应用中,JVM非标准参数的配置直接关系到整个系统的性能。 JVM非标准参数指的是JVM底层的一些配置参数,这些参数在一般开发中默认即可,不需要任何配置。但是在生产环境中,为了提高性能,往往需要调整这些参数,以求系统达到最佳新能。另外这些参数的配置也是影响系统稳定性的一个重要因素,相信大多数Java开发人员都见过“O utOfMem ory”类型的错误。呵呵,这其中很可能就是JVM参数配置不当或者就没有配置没意识到配置引起的。 为了说明这些参数,还需要说说JDK中的命令行工具一些知识做铺垫。 首先看如何获取这些命令配置信息说明: 假设你是windows平台,你安装了J2SDK,那么现在你从cmd控制台窗口进入J2SDK安装目录下的bin目录,然后运行java命令,出现如下结果,这些就是包括java.exe工具的和J VM的所有命令都在里面。 ----------------------------------------------------------------------- D:\j2sdk15\bin>java Usage: java [-options] class [args...] (to execute a class) or java [-options] -jar jarfile [args...] (to execute a jar file) where options include: -client to select the "client" VM -server to select the "server" VM -hotspot is a synonym for the "client" VM [deprecated] The default VM is client.

操作系统课程设计--连续动态分区内存管理模拟实现

(操作系统课程设计) 连续动态分区内存 管理模拟实现

目录 《操作系统》课程设计 (1) 引言 (3) 课程设计目的和内容 (3) 需求分析 (3) 概要设计 (3) 开发环境 (4) 系统分析设计 (4) 有关了解内存管理的相关理论 (4) 内存管理概念 (4) 内存管理的必要性 (4) 内存的物理组织 (4) 什么是虚拟内存 (5) 连续动态分区内存管理方式 (5) 单一连续分配(单个分区) (5) 固定分区存储管理 (5) 可变分区存储管理(动态分区) (5) 可重定位分区存储管理 (5) 问题描述和分析 (6) 程序流程图 (6) 数据结构体分析 (8) 主要程序代码分析 (9) 分析并实现四种内存分配算法 (11) 最先适应算 (11) 下次适应分配算法 (13) 最优适应算法 (16)

最坏适应算法......................................................... (18) 回收内存算法 (20) 调试与操作说明 (22) 初始界面 (22) 模拟内存分配 (23) 已分配分区说明表面 (24) 空闲区说明表界面 (24) 回收内存界面 (25) 重新申请内存界面..........................................................26. 总结与体会 (28) 参考文献 (28) 引言 操作系统是最重要的系统软件,同时也是最活跃的学科之一。我们通过操作系统可以理解计算机系统的资源如何组织,操作系统如何有效地管理这些系统资源,用户如何通过操作系统与计算机系统打交道。 存储器是计算机系统的重要组成部分,近年来,存储器容量虽然一直在不断扩大,但仍不能满足现代软件发展的需要,因此,存储器仍然是一种宝贵而又紧俏的资源。如何对它加以有效的管理,不仅直接影响到存储器的利用率,而且还对系统性能有重大影响。而动态分区分配属于连续分配的一种方式,它至今仍在内存分配方式中占有一席之地。 课程设计目的和内容: 理解内存管理的相关理论,掌握连续动态分区内存管理的理论;通过对实际问题的编程实现,获得实际应用和编程能力。

操作系统实验之内存管理实验报告

学生学号 实验课成绩 武汉理工大学 学生实验报告书 实验课程名称 计算机操作系统 开 课 学 院 计算机科学与技术学院 指导老师姓名 学 生 姓 名 学生专业班级 2016 — 2017 学年第一学期

实验三 内存管理 一、设计目的、功能与要求 1、实验目的 掌握内存管理的相关内容,对内存的分配和回收有深入的理解。 2、实现功能 模拟实现内存管理机制 3、具体要求 任选一种计算机高级语言编程实现 选择一种内存管理方案:动态分区式、请求页式、段式、段页式等 能够输入给定的内存大小,进程的个数,每个进程所需内存空间的大小等 能够选择分配、回收操作 内购显示进程在内存的储存地址、大小等 显示每次完成内存分配或回收后内存空间的使用情况 二、问题描述 所谓分区,是把内存分为一些大小相等或不等的分区,除操作系统占用一个分区外,其余分区用来存放进程的程序和数据。本次实验中才用动态分区法,也就是在作业的处理过程中划分内存的区域,根据需要确定大小。 动态分区的分配算法:首先从可用表/自由链中找到一个足以容纳该作业的可用空白区,如果这个空白区比需求大,则将它分为两个部分,一部分成为已分配区,剩下部分仍为空白区。最后修改可用表或自由链,并回送一个所分配区的序号或该分区的起始地址。 最先适应法:按分区的起始地址的递增次序,从头查找,找到符合要求的第一个分区。

最佳适应法:按照分区大小的递增次序,查找,找到符合要求的第一个分区。 最坏适应法:按分区大小的递减次序,从头查找,找到符合要求的第一个分区。 三、数据结构及功能设计 1、数据结构 定义空闲分区结构体,用来保存内存中空闲分区的情况。其中size属性表示空闲分区的大小,start_addr表示空闲分区首地址,next指针指向下一个空闲分区。 //空闲分区 typedef struct Free_Block { int size; int start_addr; struct Free_Block *next; } Free_Block; Free_Block *free_block; 定义已分配的内存空间的结构体,用来保存已经被进程占用了内存空间的情况。其中pid作为该被分配分区的编号,用于在释放该内存空间时便于查找。size表示分区的大小,start_addr表示分区的起始地址,process_name存放进程名称,next指针指向下一个分区。 //已分配分区的结构体 typedef struct Allocate_Block { int pid; int size; int start_addr; char process_name[PROCESS_NAME_LEN]; struct Allocate_Block *next; } Allocate_Block; 2、模块说明 2.1 初始化模块 对内存空间进行初始化,初始情况内存空间为空,但是要设置内存的最大容量,该内存空间的首地址,以便之后新建进程的过程中使用。当空闲分区初始化

内存管理模型的设计与实现

操作系统课程实验报告 学生姓名:尹朋 班学号:111131 指导教师:袁国斌 中国地质大学信息工程学院 2015年1月4日

实习题目:内存管理模型的设计与实现 【需求规格说明】 对内存的可变分区申请采用链表法管理进行模拟实现。要求: 1.对于给定的一个存储空间自己设计数据结构进行管理,可以使用单个链 表,也可以使用多个链表,自己负责存储空间的所有管理组织,要求采用分页方式(指定单元大小为页,如4K,2K,进程申请以页为单位)来组织基本内容; 2.当进程对内存进行空间申请操作时,模型采用一定的策略(如:首先利用 可用的内存进行分配,如果空间不够时,进行内存紧缩或其他方案进行处理)对进程给予指定的内存分配; 3.从系统开始启动到多个进程参与申请和运行时,进程最少要有3个以上, 每个执行申请的时候都要能够对系统当前的内存情况进行查看的接口; 4.对内存的申请进行内存分配,对使用过的空间进行回收,对给定的某种页 面调度进行合理的页面分配。 5.利用不同的颜色代表不同的进程对内存的占用情况,动态更新这些信息。 【算法设计】 (1)设计思想: 通过建立一个链表,来描述已分配和空闲的内存分区。对于每一个分区,它可能存放了某个进程,也可能是两个进程间的空闲区。链表中的每一个结点,分别描述了一个内存分区,包括它的起始地址、长度、指向下一个结点的指针以及分区的当前状态。 在基于链表的存储管理中,当一个新的进程到来时,需要为它分配内存空间,即为它寻找某个空闲分区,该分区的大小必须大于或等于进程的大小. 最先匹配法:假设新进程的大小为M,那么从链表的首节点开始,将每一个空闲节点的大小与M相比较,直到找到合适的节点.这种算法查找的节点很少,因而速度很快. 最佳匹配算法:搜索整个链表,将能够装得下该进程的最小空闲区分配出去. 最坏匹配法:在每次分配的时候,总是将最大的那个空闲区切去一部分,分配给请求者.它的依据是当一个很大的空闲区被切割成一部分后,可能仍然是一个比较大的空闲区,从而避免了空闲区越分越小的问题. (2)设计表示: 分区结点设计: template class ChainNode { friend Chain; public:

java虚拟机详解 免费

深入理解JVM 1 Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示: 图1 Java四个方面的关系 运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件)。最后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。从上图也可以看出Java平台由Java虚拟机和Java应用程序接口搭建,Java 语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示:

在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API,利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。 那么到底什么是Java虚拟机(JVM)呢?通常我们谈论JVM时,我们的意思可能是: 1. 对JVM规范的的比较抽象的说明; 2. 对JVM的具体实现; 3. 在程序运行期间所生成的一个JVM实例。 对JVM规范的的抽象说明是一些概念的集合,它们已经在书《The Java Virtual Machine Specification》(《Java虚拟机规范》)中被详细地描述了;对JVM的具体实现要么是软件,要么是软件和硬件的组合,它已经被许多生产厂商所实现,并存在于多种平台之上;运行Java程序的任务由JVM的运行期实例单个承担。在本文中我们所讨论的Java虚拟机(JVM)主要针对第三种情况而言。它可以被看成一个想象中的机器,在实际的计算机上通过软件模拟来实现,有自己想象中的硬件,如处理器、堆栈、寄存器等,还有自己相应的指令系统。 JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。 2 Java虚拟机的体系结构 刚才已经提到,JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。 我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

第四章 操作系统存储管理(练习题)

第四章存储管理 1. C存储管理支持多道程序设计,算法简单,但存储碎片多。 A. 段式 B. 页式 C. 固定分区 D. 段页式 2.虚拟存储技术是 B 。 A. 补充内存物理空间的技术 B. 补充相对地址空间的技术 C. 扩充外存空间的技术 D. 扩充输入输出缓冲区的技术 3.虚拟内存的容量只受 D 的限制。 A. 物理内存的大小 B. 磁盘空间的大小 C. 数据存放的实际地址 D. 计算机地址位数 4.动态页式管理中的 C 是:当内存中没有空闲页时,如何将已占据的页释放。 A. 调入策略 B. 地址变换 C. 替换策略 D. 调度算法 5.多重分区管理要求对每一个作业都分配 B 的内存单元。 A. 地址连续 B. 若干地址不连续 C. 若干连续的帧 D. 若干不连续的帧 6.段页式管理每取一数据,要访问 C 次内存。 A. 1 B. 2 C. 3 D. 4 7.分段管理提供 B 维的地址结构。 A. 1 B. 2 C. 3 D. 4 8.系统抖动是指 B。 A. 使用计算机时,屏幕闪烁的现象 B. 刚被调出内存的页又立刻被调入所形成的频繁调入调出的现象 C. 系统盘不干净,操作系统不稳定的现象 D. 由于内存分配不当,造成内存不够的现象 9.在 A中,不可能产生系统抖动现象。 A. 静态分区管理 B. 请求分页式管理 C. 段式存储管理 D. 段页式存储管理 10.在分段管理中 A 。 A. 以段为单元分配,每段是一个连续存储区 B. 段与段之间必定不连续 C. 段与段之间必定连续 D. 每段是等长的 11.请求分页式管理常用的替换策略之一有 A 。 A. LRU B. BF C. SCBF D. FPF 12.可由CPU调用执行的程序所对应的地址空间为 D 。 A. 名称空间 B. 虚拟地址空间 C. 相对地址空间 D. 物理地址空间 13. C 存储管理方式提供二维地址结构。 A. 固定分区 B. 分页

Elasticsearch Java虚拟机配置详解

JVM参数Elasticsearch默认值Environment变量 -Xms 256m ES_MIN_MEM -Xmx 1g ES_MAX_MEM -Xms and -Xmx ES_HEAP_SIZE -Xmn ES_HEAP_NEWSIZE -XX:MaxDirectMemorySize ES_DIRECT_SIZE -Xss 256k -XX:UseParNewGC + -XX:UseConcMarkSweepGC + -XX:CMSInitiatingOccupancyFraction 75 -XX:UseCMSInitiatingOccupancyOnly + -XX:UseCondCardMark (commented out) 首先你注意到的是,Elasticsearch预留了256M到1GB的堆内存。 这个设置适用于开发和演示环境。开发人员只需要简单的解压发行包,再执 行./bin/elasticsearch -f就完成了Elasticsearch的安装。当然这点对于开发来说非常棒,并且在很多场景下都能工作,但是当你需要更多内存来降低Elasticsearch负载的时候就不行了,你需要比2GB RAM更多的可用内存。

ES_MIN_MEM/ES_MAX_MEM是控制堆大小的配置。新的ES_HEAP_SIZE变量是一个更为便利的选择,因为将堆的初始大小和最大值设为相同。也推荐在分配堆内存时尽可能不要用内存的碎片。内存碎片对于性能优化来说非常不利。 ES_HEAP_NEWSIZE是可选参数,它控制堆的子集大小,也就是新生代的大小。 ES_DIRECT_SIZE控制本机直接内存大小,即JVM管理NIO框架中使用的数据区域大小。本机直接内存可以被映射到虚拟地址空间上,这样在64位的机器上更高效,因为可以规避文件系统缓冲。Elasticsearch对本机直接内存没有限制(可能导致OOM)。 由于历史原因Java虚拟机有多个垃圾收集器。可以通过以下的JVM参数组合启用: JVM parameter Garbage collector -XX:+UseSerialGC serial collector -XX:+UseParallelGC parallel collector -XX:+UseParallelOldGC Parallel compacting collector -XX:+UseConcMarkSweepGC Concurrent-Mark-Sweep (CMS) collector -XX:+UseG1GC Garbage-First collector (G1) UseParNewGC和UseConcMarkSweepGC组合启用垃圾收集器的并发多线程模式。UseConcMarkSweepGC自动选择UseParNewGC模式并禁用串行收集器(Serial collector)。在Java6中这是默认行为。 CMSInitiatingOccupancyFraction提炼了一种CMS(Concurrent-Mark-Sweep)垃圾收集设置;它将旧生代触发垃圾收集的阀值设为75.旧生代的大小是堆大小减去新生代大小。这告诉JVM当堆内容达到75%时启用垃圾收集。这是个估计的值,因为越小的堆可能需要越早启动GC。 UseCondCardMark将在垃圾收集器的card table使用时,在marking之前进行额外的判断,避免冗余的store操作。UseCondCardMark不影响Garbage-First收集器。强烈推荐在高并发场景下配置这个参数(规避card table marking技术在高并发场景下的降低吞吐量的负面作用)。在ElasticSearch中,这个参数是被注释掉的。 有些配置可以参考诸如Apache Cassandra项目,他们在JVM上有类似的需求。 总而言之,ElastciSearch配置上推荐: 1. 不采用自动的堆内存配置,将堆大小默认最大值设为1GB 2.调整触发垃圾收集的阀值,比如将gc设为75%堆大小的时候触发,这样不会影响性能。 3.禁用Java7默认的G1收集器,前提是你的ElasticSearch跑在Java7u4以上的版本上。JVM进程的内存结果 JVM内存由几部分组成: Java代码本身:包括内部代码、数据、接口,调试和监控代理或者字节码指令 非堆内存:用于加载类 栈内存:用于为每个线程存储本地变量和操作数

操作系统课程设计内存管理

内存管理模拟 实验目标: 本实验的目的是从不同侧面了解Windows 2000/XP 对用户进程的虚拟内存空间的管理、分配方法。同时需要了解跟踪程序的编写方法(与被跟踪程序保持同步,使用Windows提供的信号量)。对Windows分配虚拟内存、改变内存状态,以及对物理内存(physical memory)和页面文件(pagefile)状态查询的API 函数的功能、参数限制、使用规则要进一步了解。 默认情况下,32 位Windows 2000/XP 上每个用户进程可以占有2GB 的私有地址空间,操作系统占有剩下的2GB。Windows 2000/XP 在X86 体系结构上利用二级页表结构来实现虚拟地址向物理地址的变换。一个32 位虚拟地址被解释为三个独立的分量——页目录索引、页表索引和字节索引——它们用于找出描述页面映射结构的索引。页面大小及页表项的宽度决定了页目录和页表索引的宽度。 实验要求: 使用Windows 2000/XP 的API 函数,编写一个包含两个线程的进程,一个线程用于模拟内存分配活动,一个线程用于跟踪第一个线程的内存行为,而且要求两个线程之间通过信号量实现同步。模拟内存活动的线程可以从一个文件中读出要进行的内存操作,每个内存操作包括如下内容: 时间:操作等待时间。 块数:分配内存的粒度。 操作:包括保留(reserve)一个区域、提交(commit)一个区域、释放(release)一个区域、回收(decommit)一个区域和加锁(lock)与解锁(unlock)一个区域,可以将这些操作编号存放于文件。保留是指保留进程的虚拟地址空间,而不分配物理 存储空间。提交在内存中分配物理存储空间。回收是指释放物理内存空间,但在虚拟地址空间仍然保留,它与提交相对应,即可以回收已经提交的内存块。释放是指将物理存储和虚拟地址空间全部释放,它与保留(reserve)相对应,即可以释放已经保留的内存块。 大小:块的大小。 访问权限:共五种,分别为PAGE_READONLY,PAGE_READWRITE ,PAGE_EXECUTE,PAGE_EXECUTE_READ 和PAGE EXETUTE_READWRITE。可以将这些权限编号存放于文件中跟踪线程将页面大小、已使用的地址范围、物理内存总量,以及虚拟内存总量等信息显示出来。

深入理解Java虚拟机笔记(带目录)

目录 1.虚拟机内存结构 (1) 2.对象在内存中的布局 (3) 3.判断对象是否死亡 (4) 4.引用的4中情况 (4) 5.垃圾收集算法 (5) 6.HotSpot虚拟机算法实现 (6) 7.如何在GC发生时让所有线程都要附近的安全点停下 (6) 8.垃圾收集器 (7) 9.GC日志 (9) 10.内存分配 (10) 11.Class类文件的结构 (10) 12.类的生命周期 (13) 13.类加载器 (15) 14.运行时栈帧的结构 (16) 15. 方法调用 (18) 16. 分派 (19) 17.虚方法表 (19) 18.Java内存模型(JMM) (19) 19.内存间的交互 (20) 20.volatile变量 (20) 21.原子性 (21) 22.可见性 (22) 23.有序性 (22) 24.先行发生原则 (22) 25.Java线程调度 (23) 26.线程的状态 (24) 27.线程安全 (25) 28.线程安全的实现方法 (26) 29.锁优化 (27) 30.编译优化技术 (29) 1.虚拟机内存结构 线程私有:虚拟机栈,本地方法栈,程序计数器 线程共享:堆,方法区(包括运行时常量池)

1.1程序计数器 当前程序锁执行的字节码行号指示器,记录下一条需要执行的 指令。 1.2虚拟机栈 生命周期与线程相同,每个方法在执行时都会创建一个栈帧。 方法执行的过程,就是栈帧入栈到出栈的过程。 栈帧用于存放局部变量表,操作数栈,动态链接,方法出口等 信息。 局部变量表存放了编译期可知的基本数据类型和对象引用。1.3 本地方法栈 为虚拟机使用到的Native方法服务。 目前HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。 1.4堆 存放对象实例,所有线程共享。 1.5 方法区(永久代) 存放被虚拟机加载的类信息,常量,静态变量,即时编译器编 译后的代码等。

实验五动态页式存储管理实现过程的模拟

实验五动态页式存储管理实现过程的模拟 一、实验目的与要求 在计算机系统中,为了提高主存利用率,往往把辅助存储器(如磁盘)作为主存储器的扩充,使多道运行的作业的全部逻辑地址空间总和可以超出主存的绝对地址空间。用这种办法扩充的主存储器称为虚拟存储器。通过本实验帮助学生理解在分页式存储管理中怎样实现虚拟存储器;掌握物理内存和虚拟内存的基本概念;掌握重定位的基本概念及其要点,理解逻辑地址与绝对地址;掌握动态页式存储管理的基本原理、地址变换和缺页中断、主存空间的分配及分配算法;掌握常用淘汰算法。 二、实验环境 VC++6.0集成开发环境或java程序开发环境。 三、实验内容 模拟分页式虚拟存储管理中硬件的地址转换和缺页中断,以及选择页面调度算法处理缺页中断。 四、实验原理 1、地址转换 (1)分页式虚拟存储系统是把作业信息的副本存放在磁盘上,当作业被选中时,可把作业的开始几页先装入主存且启动执行。为此,在为作业建立页表时,应说明哪些页已在主存,哪些页尚未装入主存,页表的格式如图10所示: 图10 页表格式 其中,标志----用来表示对应页是否已经装入主存,标志位=1,则表示该页已经在主存,标志位=0,则表示该页尚未装入主存。 主存块号----用来表示已经装入主存的页所占的块号。

在磁盘上的位置----用来指出作业副本的每一页被存放在磁盘上的位置。 (2)作业执行时,指令中的逻辑地址指出了参加运算的操作存放的页号和单元号,硬件的地址转换机构按页号查页表,若该页对应标志为“1”,则表示该页已在主存,这时根据关系式: 绝对地址=块号×块长+单元号 计算出欲访问的主存单元地址。如果块长为2的幂次,则可把块号作为高地址部分,把单元号作为低地址部分,两者拼接而成绝对地址。若访问的页对应标志为“0”,则表示该页不在主存,这时硬件发“缺页中断”信号,有操作系统按该页在磁盘上的位置,把该页信息从磁盘读出装入主存后再重新执行这条指令。 (3)设计一个“地址转换”程序来模拟硬件的地址转换工作。当访问的页在主存时,则形成绝对地址,但不去模拟指令的执行,而用输出转换后的地址来代替一条指令的执行。当访问的页不在主存时,则输出“* 该页页号”,表示产生了一次缺页中断。该模拟程序的算法如图11。 图11 地址转换模拟算法 2、用先进先出(FIFO)页面调度算法处理缺页中断。

操作系统内存管理原理

内存分段和请求式分页 在深入i386架构的技术细节之前,让我们先返回1978年,那一年Intel 发布了PC处理器之母:8086。我想将讨论限制到这个有重大意义的里程碑上。如果你打算知道更多,阅读Robert L.的80486程序员参考(Hummel 1992)将是一个很棒的开始。现在看来这有些过时了,因为它没有涵盖Pentium处理器家族的新特性;不过,该参考手册中仍保留了大量i386架构的基本信息。尽管8086能够访问1MB RAM的地址空间,但应用程序还是无法“看到”整个的物理地址空间,这是因为CPU寄存器的地址仅有16位。这就意味着应用程序可访问的连续线性地址空间仅有64KB,但是通过16位段寄存器的帮助,这个64KB大小的内存窗口就可以在整个物理空间中上下移动,64KB逻辑空间中的线性地址作为偏移量和基地址(由16位的段寄存器给处)相加,从而构成有效的20位地址。这种古老的内存模型仍然被最新的Pentium CPU支持,它被称为:实地址模式,通常叫做:实模式。 80286 CPU引入了另一种模式,称为:受保护的虚拟地址模式,或者简单的称之为:保护模式。该模式提供的内存模型中使用的物理地址不再是简单的将线性地址和段基址相加。为了保持与8086和80186的向后兼容,80286仍然使用段寄存器,但是在切换到保护模式后,它们将不再包含物理段的地址。替代的是,它们提供了一个选择器(selector),该选择器由一个描述符表的索引构成。描述符表中的每一项都定义了一个24位的物理基址,允许访问16MB RAM,在当时这是一个很不可思议的数量。不过,80286仍然是16位CPU,因此线性地址空间仍然被限制在64KB。 1985年的80386 CPU突破了这一限制。该芯片最终砍断了16位寻址的锁链,将线性地址空间推到了4GB,并在引入32位线性地址的同时保留了基本的选择器/描述符架构。幸运的是,80286的描述符结构中还有一些剩余的位可以拿来使用。从16位迁移到32位地址后,CPU的数据寄存器的大小也相应的增加了两倍,并同时增加了一个新的强大的寻址模型。真正的32位的数据和地址为程序员带了实际的便利。事实上,在微软的Windows平台真正完全支持32位模型是在好几年之后。Windows NT的第一个版本在1993年7月26日发布,实现了真正意义上的Win32 API。但是Windows 3.x程序员仍然要处理由独立的代码和数据段构成的64KB内存片,Windows NT提供了平坦的4GB地址空间,在那儿可以使用简单的32位指针来寻址所有的代码和数据,而不需要分段。在内部,当然,分段仍然在起作用,就像我在前面提及的那样。不过管理段的所有责任都被移给了操作系统。

深入理解java虚拟机

深入理解java虚拟机 (一)虚拟机内存划分 Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区。这些区域有不同的特性,起不同的作用。它们有各自的创建时间,销毁时间。有的区域随着进程的启动而创建,随着进程结束而销毁,有的则始终贯穿虚拟机整个生命周期。 Java虚拟机运行时内存区域主要分为七部分,分别是:程序计数器,Java虚拟机栈,本地方法栈,方法区,Java堆,运行时常量池,直接内存。 如上图所示(图片来源于网络): 蓝色区域包裹的部分为运行时几个数据区域: 白色的部分为线程私有的,既随着线程的启动而创建。每个线程都拥有各自的一份内存区域。它们是:JAVA栈(JAVA STACK),本地方法栈(NATIVE METHOD STACK),和程序计数器(PROGRAM COUNTER REGISTER)。 黄色部分是线程共享的,所有的线程共享该区域的内容。他们是: 方法区(METHOD AREA),堆(HEAP)。 我们分别来介绍这些区域。 (1)程序计数器(program counter register)

学过计算机组成原理的都知道计算机处理器中的程序计数器。当处理器执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。 处理器的程序计数器是指寄存器,而java程序计数器是指一小块内存空间。java代码编译字节码之后,虚拟机会一行一行的解释字节码,并翻印成本地代码。这个程序计数器盛放的就是当前线程所执行字节码的行号的指示器。在虚拟机概念模型中,字节码解释器工作室就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理等都依赖于它。 Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,因此为了线程切换后还能恢复执行位置,每条线程都需要一个独立的程序计数器。 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Java Native方法,这个计数器值为空。 而且程序计数器是Java虚拟机中没有规定任何OutOfMemoryError的区域。 (2)虚拟机栈 Java虚拟机栈(VM Stack)也是线程私有的,因此它的生命周期也和线程相同。它存放的是Java方法执行时的数据,既描述的是Java方法执行的内存模型:每个方法开始执行的时候,都会创建一个栈帧(Stack Frame)用于储存局部变量表、栈操作数、动态链接、方法出口等信息。每个方法从调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。经常有人把Java内存分为堆内存和栈内存,这种是比较粗糙的分法,很大原因是大多数程序‘猿’最关注的,与对象内存分配最密切的区域就是堆和栈。局部变量表存放的是编译器可知的各种基本数据类型(boolean 、byte、int、long、char、short、float、double)、对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)。其中64bit长度的long和double会占用两个局部变量空间(Slot),其余的数据类型只占用一个。局部变量表所需的内存空间是在编译时期确定的,在方法运行期间不会改变局部变量表的大小。在Java虚拟机规范中,对这部分区域规定了两种异常:1、当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常; 2、如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。 (3)本地方法栈 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是十分相似的,他们之间的区别不过是虚拟机栈为Java方法字节码服务,而本地方法栈则为Native方法服务。在虚拟机规范中对本地方法使用的语言和使用方法与数据结构没有强制规定,因此具体的虚拟机可

实验四 内存管理模拟实验

实验四内存管理模拟实验 模拟实现一个简单的固定(可变)分区存储管理系统 1.实验目的 通过本次课程设计,掌握了如何进行内存的分区管理,强化了对首次适应分配算法和分区回收算法的理解。 2.实验内容 (1)建立相关的数据结构,作业控制块、已分配分区及未分配分区 (2)实现一个分区分配算法,如最先适应算法、最优或最坏适应分配算法 (3)实现一个分区回收算法 (4)给定一个作业/进程,选择一个分配或回收算法,实现分区存储的模拟管理 图1.流程图

3.实验步骤 首先,初始化函数initial()将分区表初始化并创建空闲分区列表,空闲区第一块的长度是30,以后的每个块长度比前一个的长度长20。 frees[0].length=30 第二块的长度比第一块长20,第三块比第二块长20,以此类推。 frees[i].length=frees[i-1].length+20; 下一块空闲区的首地址是上一块空闲区的首地址与上一块空闲区长度的和。frees[i].front=frees[i-1].front+frees[i-1].length; 分配区的首地址和长度都初始化为零occupys[i].front=0;occupys[i].length=0; 显示函数show()是显示当前的空闲分区表和当前的已分配表的具体类容,分区的有起始地址、长度以及状态,利用for语句循环输出。有一定的格式,使得输出比较美观好看。 assign()函数是运用首次适应分配算法进行分区,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;然后再按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。这个算法倾向于优先利用内存中低址部分被的空闲分区,从而保留了高址部分的的大空闲区。着给为以后到达的大作业分配大的内存空间创造了条件。它的缺点是低地址部分不断被划分,会留下很多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这样无疑会增加查找可用空闲分区的开销。 分配内存,从空闲的分区表中找到所需大小的分区。设请求的分区的大小为job_length,表中每个空闲分区的大小可表示为free[i].length。如果frees[i].length>=job_length,即空闲空间I的长度大于等于作业的长度将空闲标志位设置为1,如果不满足这个条件则输出:“对不起,当前没有满足你申请长度的空闲内存,请稍后再试!”。如果frees[i].length>=job_length空闲区空间I的长度不大于作业长度,I的值加1判断下一个空闲区空间是否大于作业的长度。把未用的空闲空间的首地址付给已用空间的首地址,已用空间的长度为作业的长度,已用空间数量加1。如果(frees[i].length>job_length),空间的长度大于作业的长度,frees[i].front+=job_length; 空闲空间的起始首地址=原空闲区间的起始长度加作业长度frees[i].length-=job_length;空闲区间的长度=原空闲区间的长度-作业的长度。如果空间的长度与作业的长度相等,空闲区向前移一位,空闲区的数量也减一。这样判断所有情况并相应分配之后,内存空间分配成功。 第二个操作为:撤消相应作业。在这个操作中,进行了以下步骤: (1)按照系统提示输入将要撤消的作业名; (2)判断该作业是否存在 若不存在:输出“没有这个作业名,请重新输入作业名”; 若存在:则先分别用flag,start,len保存该作业在分配区表的位置i,内存空间的首地址以及长度。接着根据回收区的首地址,即该作业的首地址,从空闲区表中找到相应的插入点,将其加入空闲表,此时可能出现以下三种情况之一: 1 .回收区只与插入点前一个空闲分区F1相邻接即(frees[i].front+frees[i].length)==start),此时判断其是否与后一个空闲分区F2相邻接,又分两种情况: 若相邻接,则将三个分区合并,修改新的空闲分区的首地址和长度。新的首地址为F1的首地址,长度为三个分区长度之和,相应的代码为:

操作系统 内存管理实验报告

同组同学学号: 同组同学姓名: 实验日期:交报告日期: 实验(No. 4 )题目:编程与调试:内存管理 实验目的及要求: 实验目的: 操作系统的发展使得系统完成了大部分的内存管理工作,对于程序员而言,这些内存管理的过程是完全透明不可见的。因此,程序员开发时从不关心系统如何为自己分配内存,而且永远认为系统可以分配给程序所需的内存。在程序开发时,程序员真正需要做的就是:申请内存、使用内存、释放内存。其它一概无需过问。本章的3个实验程序帮助同学们更好地理解从程序员的角度应如何使用内存。 实验要求: 练习一:用vim编辑创建下列文件,用GCC编译工具,生成可调试的可执行文件,记录并分析执行结果,分析遇到的问题和解决方法。 练习二:用vim编辑创建下列文件,用GCC编译工具,生成可调试的可执行文件,记录并分析执行结果。 练习三:用vim编辑创建下列文件,用GCC编译工具,生成可调试的可执行文件,记录并分析执行结果。 改编实验中的程序,并运行出结果。 实验设备:多媒体电脑 实验内容以及步骤: 在虚拟机中编写好以下程序: #include #include #include int main(void) { char *str; /* 为字符串申请分配一块内存*/ if ((str = (char *) malloc(10)) == NULL) { printf("Not enough memory to allocate buffer\n"); return(1); /* 若失败则结束程序*/ } /* 拷贝字符串“Hello”到已分配的内存空间*/ strcpy(str, "Hello"); /* 显示该字符串*/ printf("String is %s\n", str); /* 内存使用完毕,释放它*/ free(str); return 0; } 调试过后得出的结果截图如下:(由图可看出我将此程序以aa.c为文件名保存,调试后出现aa1文件,调试结果出现语句“String is Hello”)

Java虚拟机的内存结构

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类。另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收。如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,那么内存回收也就可以根据每个区域的特定进行回收,比如像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就需要使用经典的回收算法来进行回收了,所以看起来分类这么麻烦,其实是大有好处的。 提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。但是·虚拟机的内存结构远比此要复杂的多。除了我们所认识的(还没有认识完全)的堆栈以外,还有程序计数器,本地方法栈和方法区。我们平时所说的栈内存,一般是指的栈内存中的局部变量表。下面是官方所给的虚拟机的内存结构图

从图中可以看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,非常好理解,我们知道每一个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被所有线程一起使用的,不管有多少个线程,这片空间始终就这一个。而线程的独占区,是每个线程都有这么一份内存空间,每个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不代表实际内存区域的大小,实际运行过程中,内存区域的大小也是可以动态调整的。下面来具体说说每一个区域的主要功能。

相关主题
文本预览
相关文档 最新文档