JVM中的垃圾收集_第1页
JVM中的垃圾收集_第2页
JVM中的垃圾收集_第3页
JVM中的垃圾收集_第4页
JVM中的垃圾收集_第5页
已阅读5页,还剩15页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Java垃圾收集算法简介任何垃圾收集算法必须完成两件事情。首先,它必须检测出垃圾对象。其次,它必须回收垃圾对象所占用的堆空间并使之对程序重新可用。垃圾检测通常通过定义一个根引用集并计算其可达对象集的方式来实现。一个对象,如果可以通过某条始于根引用的引用路径而被执行程序访问到的话,则称其 为可达的(reachable)。对程序而言,根引用始终是可以访问的。一个对象如果是可达的,则称其为活动对象;否则就被称为垃圾,因为它对程序的未来 执行不再有任何影响。根引用集的构成取决于的具体实现,但总是包含所有栈帧中局部变量区和操作数栈所持有的引用以及保存在静态变量中的引用。根引用的另外一个来源是 常量池,已

2、装载类的常量池可能保存有对堆中一些字符串的引用,这些字符串往往是类名、父类名、父接口名、域名、域签名、方法名和方法签名等。根引用还有一 个可能来源就是那些传递给本地方法(native method)但尚未被其释放的引用。根引用的另一个潜在来源是的运行时数据区,因为某些实现会把运行时数据区的一部分放在堆上,例如方法区 中的类数据本身。可达对象集包含所有通过根引用可以直接或间接被程序访问到的对象,从技术角度讲,可达对象集是“指向”关系下根引用集的传递闭包。区分活动对象和垃圾的两个基本方法是引用计数和追踪。中的标准垃圾收集器全部采用了追踪的办法,虽然具体形式各不相同。一、引用计数收集器引用计数是垃圾

3、收集的早期策略。在这种方法中,堆中的每个对象都有一个引用计数。当对象被创建并且指向其引用被赋值给一个变量后,该对象的引用计数被 设置为。以后每当其引用被赋值给一个不同的变量时,该对象的引用计数就加。当持有该对象引用的变量离开其作用域或者被赋给一个新值时,该对象的引用计 数就减。任一对象一旦其引用计数变为,就成为垃圾。一个对象一旦被当作垃圾收集后,它所引用的所有其他对象的引用计数必须相应递减。这样,对一个对象 的垃圾收集可能引发连续的对其他对象的垃圾收集。该方法的好处是,引用计数收集器算法简单,适于做增量收集,对于程序不能被长时间打断的实时环境特别适合,另外,收集过程也有助于改进引用局部性。坏

4、处就是,引用计数无法检测出不可达的循环结构(两个或多个对象之间相互引用),因为它们的引用计数永远不会为。另一个坏处就是每次增减引用计数都带来额 外开销,而且该算法还需要编译器的高度配合。正是由于这些固有缺陷,引用计数算法在生产环境中很少使用。二、追踪收集器追踪收集器从根引用开始探寻并描画对象引用图。探寻过程中遇到的对象会以某种方式被打上标记。通常来说该标记既可保存在对象本身,也可保存在单独的位图中。探寻结束后未被标记的对象就是不可达的对象,可被当作垃圾收集。基本的追踪算法被称作“标记并清理”(mark and sweep)。该名字指出了垃圾收集过程的两个阶段。在标记阶段,垃圾收集器遍历引用树,

5、标记每一个遇到的对象。在清理阶段,未被标记的对象被释放,相应内存被返还待用。在中,清理阶段必须包括对象的了结(finalization)。标记并清理算法实现简单,可以轻易回收循环结构,而且不存在为维护引用计数而付出的额外开销和对编译器的依赖。但是它也有不足,其中最大的问题是,在 清理阶段,堆中的所有对象,不论是否可达,都会被访问。一方面这对于可能有页面交换的堆所依赖的虚存系统有着非常负面的性能影响;另一方面,因为其中很大 一部分对象可能是垃圾,这就意味着垃圾收集器把大量精力都花费在检查和处理垃圾上面了。无论从哪个角度来看,该算法都可能产生收集暂停时间过长、收集开销 偏大的问题。标记并清理收集器

6、的另一个不足是它容易导致堆的碎片化,从而引发引用局部性或者大对象分配失败等方面的问题。三、压缩收集器的垃圾收集器很可能拥有一个对付堆碎片的策略。标记并清理收集器通常采用的两种 策略是压缩或拷贝。这两种方法都是通过快速移动对象来减少堆碎片。压缩收集器把活动对象越过空闲内存区滑动到堆的一端,在这个过程中,堆的另一端就变成了 一块大的连续空闲区。所有指向被移动对象的引用也被更新,指向新的位置。为了更好的理解压缩过程,可以将堆比作书架的一格,其中一部分放满了不同厚度的图书。空闲空间就是图书之间的空隙。压缩就是将所有图书朝一个方向推 移,以弥合所有空隙。它从最靠近隔板的图书开始,将它推向隔板,然后将离隔

7、板第二近的图书推向第一本图书,接着将第三本图书推向第二本图书,依此类推。最 后,所有图书在一端,所有空闲空间在另一端。被移动对象的引用更新可以通过为对象引用添加一层间址而得到简化。对象引用不再直接指向堆中的对象,而是指向对象句柄表中的一个表项,该表项中的对象 句柄才直接指向堆中的实际对象。这样,当对象被移动时,只需在对象句柄表中更新其句柄,执行程序中所有指向该对象的引用都不必再更新。这种方法简化了消除 堆碎片的工作,但增加了每一次对象存取的开销。四、拷贝收集器拷贝收集器同样使用追踪技术,它把所有的活动对象移动到一个新的区域,而原有区域就全 部变成了空闲空间。由于被移动对象在新的区域中被紧挨着放

8、置,因而在原有区域时对象之间可能存在的空隙也被消除。对象的拷贝可以在追踪过程中即时进行,不 必通过标记和清理两个单独的阶段来完成。对象被实时拷贝到新的区域后,它在原有区域中的副本被一个转向指针(forwarding pointer)所取代,该指针指向该对象在新的区域中的副本。转向指针让垃圾收集器检测出那些引用(其所指对象已经被移动到新的区域中)并以转向指针的 值更新之,从而使它们指向对象的新位置。一个通用的拷贝收集器算法被称作“停止并拷贝”(stop and copy)。在这个方案中,堆被分成两个区域,任何时候都只使用其中的一个区域。对象在同一区域中分配,直到该区域的所有空间被耗尽。此时,程序

9、执行被中 止,堆被遍历,遍历过程中遇到的活动对象被拷贝到堆的另一个区域。当停止并拷贝过程完成后,程序恢复执行,对象的内存将从堆的这个新区域中分配,直到它也 被用尽。那时程序将被再次中止,堆被遍历,活动对象被拷贝回原来的区域。该方案的代价是所需内存是指定堆空间的两倍,因为不论何时都只有一半的内存被使 用。拷贝收集算法的优点是只访问活动对象,垃圾对象不会被检查,自然也无需被换页到内存或被缓存,收集过程所用时间只取决于活动对象的数量。这样既避免了 不必要的收集开销,又最大限度地减少了收集暂停时间。不过,除了额外的内存消耗外,拷贝收集器还需要承担对象拷贝和引用更新所带来的成本增加。这一点在长 寿对象较

10、多时体现得更为明显,因为每次收集时它们都要被来回复制。五、分代收集器通过观察和实验,对多种语言所写的大多数应用程序而言,它们所创建的对象都具有如下特征:)大部分对象的寿命都很短,但总有一些对象会活得足够长;)年长对象很少引用年幼对象。以上事实也被称作“弱代假说”(weak generational hypothesis),它是分代收集算法的前提和基础。在这种方法中,对象按照年龄分成组(代),堆则被划分成两个或多个子堆,每个子堆服务于一代对象,因此子堆也常常被称作某某代。最年幼代被进行最频繁 的垃圾收集。因为大部分对象的寿命都很短,所以只有很小一部分的最年幼对象经历首次收集后还能存活。如果一个最

11、年幼对象在经历几次垃圾收集后依然存活,那 么它将被提升到寿命更高的一代(被移动到另一个子堆中去)。与相对年幼代比较,相对年长代被垃圾收集的频率总会有所降低。随着对象在其当前代中的不断成熟 (经历多次垃圾收集而不死),最终它们会被移动到较其当前代更为年长的一代中。分代收集技术既可应用于拷贝算法,以解决它在处理长寿对象时效率低下的问题,也可应用于标记并清理算法。不管在哪种情况下,把堆划分为对象代都有助于提高最基本的垃圾收集算法的效率。六、自适应收集器自适应收集算法利用如下事实:一些收集算法在某些情况下工作的更好,而另一些则在其 他情况下工作的更好。自适应收集器监控堆的当前状态并据此对其所用垃圾收集

12、技术做相应调整。它可能只在程序运行的同时对单一收集算法的参数做出调整,也可 能从一种算法快速切换到另一种算法,甚或还可能把堆划分为子堆并在不同的子堆上同时使用不同的算法。使用自适应方法,实现的设计者无需再选择某种特定的垃圾收集技术。他们可以使用多种技术,为每种算法分配最适合它的工作。实际上,在现代实现中,大部分垃圾收集子系统都具有某种程度的自适应能力,很多时候我们只须选择策略并设定目标便能得到满意的结果,至于具体的收集算法选择和参数配置则可交由垃圾收集子系统自行处理。的HotSpot JVM中的垃圾处理子系统就是这样运作的,具体细节有时间再专门讨论。HotSpotJVM中的垃圾收集(2010-

13、03-25 05:51:49) 从J2SE 5.0开始,HotSpot JVM共包含四种垃圾收集器,它们全部基于分代算法。一、代的划分HotSpot JVM中内存被划分为三代:年幼代(young generation)、年长代(old generation)和永久代(permanent generation)。从逻辑上讲,年幼代和年长代共同构成了Java堆,而永久代则被称为方法区(method area)。除了一些大对象可能在年长区中直接分配外,大部分对象都在年幼区中创建;而年长区除了那些直接创建的大对象外,大部分对象都是在年幼区中历经 几次垃圾收集而幸免于难后被提升过来的。永久代中则保存着已

14、载入类型的相关信息,包含了类、方法和其他一些内部使用的元数据,所有这些信息同样以对象的形 式来组织和表示,虽然这些对象并不是Java对象,但是它们却象Java对象一样可以被同样的垃圾收集器所收集;另外,java.lang.String 类所管理的内在化的字符串缓存池也在该代中分配;虽然名字叫做“永久”代,但其中的对象并不是永久的,只是沿用了历史名称而已。年幼代由一个伊甸区(Eden Space)和两个更小的生还区(Survivor Space)组成,如下图所示(该图为缩略图,请点击察看原图)。大部分对象在伊甸区中创建,少数大对象可能在年长代中直接创建。生还区中保存的对象是历 经一次或多次对年幼

15、代的垃圾收集而未死的幸存者,并且在被认为已足够成熟而提升到年长代之前,它们仍有在稍后的某次垃圾收集过程中牺牲的可能。除非处在垃 圾收集过程当中,两个生还区中只有一个用来容纳这些幸存者,另一个则保持为空。二、垃圾收集类型年幼代填满后,一次只针对该代的收集被执行,这样的收集也被称作“次收集(minor collection)”。当年长代或永久代被填满后,一次针对所有代的完整收集被执行,这样的收集也被称作“主收集(major collection)”。通常来说,在一次主收集过程中,年幼代首先被收集,收集算法采用当前收集器的年幼代收集算法,该算法往往是针对年幼对象的行为 特征而专门设计的;然后是对年长

16、代和永久代的收集,收集算法都采用当前收集器的年长代收集算法。对于给定收集器所具体使用的年幼代和年长代收集算法,请参 考下文。另外,主收集过程中如果存在压缩,则每代独自进行。不过,首先收集年幼代的策略在年长代空闲空间太小时会失效,因为年长代已无足够的空间来接纳所有的可能从年幼代提升过来的对象;在这种情况下,除 CMS外的所有收集器都会放弃原有的年幼代收集算法,转而统一采用年长代收集算法对所有代进行收集。(CMS收集器之所以例外是因为它的年长代算法不能用 来收集年幼代。)三、快速分配从下文对垃圾收集器的描述中可以看出,在许多情况下,内存中都有大块的连续空闲空间用以满足对象的分配请求。这种情形下的分

17、配操作使用简单的 “bump-the-pointer”技术,效率很高。按照这种技术,JVM内部维护一个指针(allocatedTail),它始终指向先前已分配对象 的尾部,当新的对象分配请求到来时,只需检查代中剩余空间(从allocatedTail到代尾geneTail)是否足以容纳该对象,并在“是”的情况 下更新allocatedTail指针并初始化对象。下面的伪代码具体展示了从连续内存块中分配对象时分配操作的简洁性和高效性:void *malloc(int n) if( geneTail - allocatedTail n ) doGarbageCollection();void * wa

18、sAllocatedTail = allocatedTail;allocatedTail += n; return wasAllocatedTail;对于多线程应用,分配操作必须是线程安全的。如果使用全局锁为此提供保证,则分配操作必定成为一个性能瓶颈。基于 此,HotSport JVM采用了一种被称为“线程局部分配缓冲区”(Thread-Local Allocation Buffers,TLAB)的技术。该项技术为每个线程提供一个独立的分配缓冲区(伊甸区的一小部分),借此来提高分配操作的吞吐量。因为针对每个 TLAB,只有一个线程从中分配对象,故而分配操作可以使用“bump-the-point

19、er”技术快速完成,而不必使用任何锁机制;只有当线程将其已 有TLAB填满并且需要获取一个新的TLAB时,同步才是必须的。同时,为了减少TLAB所带来的空间消耗,还使用了一些其他技术,例如,分配器能够把 TLAB的平均大小限制在伊甸区的以下。“bump-the-pointer”和TLAB技术的组合保证了分配操作的高效性,类似new Object()这样的操作在大部分时间内只需要大约10条机器指令即可完成。四、收集方式)串行(serial)和并行(parallel)串行和并行是从收集任务本身如何被完成的角度来描述收集过程的。采用串行方式收集时,同一时间只有一件事情会发生。例如,即使有多个可用,还

20、是 只有一个被用来执行收集任务。当采用并行方式收集时,垃圾收集任务被分成许多子任务,并且那些子任务在不同的上被同时执行。同时操作使收集任务得以 更快地完成,代价是增加了任务的复杂性并可能产生内存碎片。)(stop-the-world)和并发(concurrent)和并发是从收集任务是否影响应用程序执行的角度来描述收集过程的。当垃圾收集使用方式进行时,在收集期间应用程序将被完全挂起。作为另一 种选择,收集任务可以采取并发方式,与应用程序一起同时执行。比较典型的情况是,并发收集器采取并发方式完成大部分工作,但也可能偶尔地不得不切换到 方式,以完成一些需要应用程序短暂停顿的工作。收集比并发收集更为简

21、单,因为在收集期间堆被冻结、对象不会改变;它的缺点是对某些应用程序来 说,被暂停过久是不符合要求的。相对地,采用并发方式进行垃圾收集时,停顿时间会更短,但这也要求收集器必须更加小心,因为它正在操作可能被应用程序同时 更新的对象。对并发收集器来说,这会增加额外开销从而影响性能,也会对堆空间产生更大的需求。五、串行收集器(serial collector)使用串行收集器时,对年幼代和年长代的收集都采用串行、方式进行。也就是说收集任务同时只使用一个,而且在收集任务执行期间,应用程序的执行被中止。)串行收集器如何收集年幼代?下图展示了使用串行收集器对年幼代进行收集时的操作细节。伊甸区中的活动对象被拷贝

22、到初始为空的生还区中;已占用的生还区中的活动对象如果仍显年 轻,则同样被拷贝到生还区中,否则被直接拷贝到年长代;需要注意的是,生还区一旦被填满,则所有尚未被拷贝的活动对象,不论其来自伊甸区还是生还区 ,也不论其曾经幸免于多少次次收集,统统被拷贝到年长代。按照定义,在活动对象被拷贝之后,伊甸区和生还区中的对象就全部成为垃圾对象,无须再被检 查。(垃圾对象在图中以“”标记,虽然实际上收集器并不检查和标记这些对象。) 收集完成后,伊甸区和生还区变为空闲空间,活动对象保存在生还区中。此时,两个生还区的角色已经发生了互换。如下图:)串行收集器如何收集年长代?串行收集器采用标记-清理-压缩算法收集年长代和

23、永久代。在标记阶段,收集器遍历引用树,找出所有活动对象并打上标记;在清理阶段,顺序扫描代空间中 所有对象(不论死活),计算出每个活动对象在代空间中的新位置;在压缩阶段,指向活动对象的所有引用被先期更新后,所有活动对象也被逐个滑动到其新的位 置。由于所有活动对象都是按照次序朝代空间头部移动的,因此就在代空间尾部自然形成了一个单一而连续的空闲空间。如下图:压缩过程使基于年长代或永久代的分配操作可以使用快速的“bump-the-pointer”技术。)何时使用串行收集器?对于运行在客户端级硬件上并且对停顿时间没有特别要求的大多数应用而言,串行收集器都是首选。按照目前的硬件水平,串行收集器可以高效地管

24、理使用堆空间、最长停顿时间不能超过半秒的很多重要应用。)串行收集器的选用在J2SE 5.0版本中,对于非服务器级硬件而言,串行收集器作为缺省的垃圾收集器被自动选用;对于其他硬件平台,则可以通过命令行选项“-XX:+UseSerialGC”进行显示的选用。六、并行收集器(parallel collector)目前,许多Java应用的运行平台大都包含很多物理内存和多个。并行收集器,也被称作吞吐量收集器,被开发出来的主要目的就是为了充分利用资源,而不是只让一个去做垃圾收集而其他却被闲置。)并行收集器如何收集年幼代?和串行收集器相比,并行收集器采用了大致相同的年幼代收集算法,只是执行的是其并行版本而已

25、。对年幼代的收集虽然仍基于拷贝技术、采用方式进 行,但收集工作是并行展开的,使用了多个,这就降低了垃圾收集开销,从而提高了应用程序的吞吐量。下图展示了并行收集器和串行收集器在执行年幼代收 集时到底有何不同:)并行收集器如何收集年长代?和串行收集器一样,并行收集器对年长代的收集同样基于标记-清理-压缩算法,同样采用串行、方式进行。)何时使用并行收集器?能够得益于并行收集器的应用程序,必定运行在多机器上,并且对停顿时间不能有特别的约束。因为可能持续时间很长的年长代收集虽然稀少,但还是会 发生的。适于采用并行收集器的典型应用包括批处理、记帐、工资单和科学计算等等。你可能更倾向于选择并行压缩收集器(见

26、下文)而不是并行收集器,因为前者 对所有代(而不只是年幼代)的收集都采用并行方式进行。)并行收集器的选用在J2SE 5.0版本中,对于服务器级硬件而言,并行收集器作为缺省的垃圾收集器被自动选用;对于其他硬件平台,则可以通过命令行选项“-XX:+UseParallelGC”进行显示的选用。七、并行压缩收集器(parallel compacting collector)并行压缩收集器在J2SE 5.0 U6中引入,它和并行收集器的区别在于,对年长代的收集它使用了全新的算法。注意:并行压缩收集器终将取代并行收集器。)并行压缩收集器如何收集年幼代?同并行收集器,不再赘述。)并行压缩收集器如何收集年长代

27、?使用并行压缩收集器时,对年长代和永久代的收集都采用带滑动压缩的准并行、方式进行。为了满足并行处理的要求,每一个代空间均被逻辑划分为诸多 定长区域(fixed-sized region),每个区域的相关信息保存在收集器维护的内部数据结构中。收集过程被分为标记、汇总和压缩三个阶段进行。在标记阶段,根引用集被划分给多个 垃圾收集线程,它们同时运行,以并行的方式对活动对象进行追踪和标记;在活动对象被标记的同时,该对象的起始区域的数据也将被同步更新以反映该活动对象的 大小和位置信息。在汇总阶段(summary phase),操作不再基于对象,而是区域。考虑到先前收集过程中的压缩累积效应,每一个代空间中

28、位于左侧的某一部分通常是密集的,主要包含了活动对象。 从这样的密集区块中可能回收的空间数量使得它们并不值得被压缩。所以汇总阶段的首要任务就是检查区域的密集度,从最左边一个区域开始,直到找到这样的一个 区域,使得在该区域及其右侧所有区域中可被回收的空间数量抵得上对它们进行压缩的成本。该区域左侧的所有区域就被称为密集前置区块,没有对象会被移入其 中。该区域及其右侧所有区域会被压缩,以消除所有死区。汇总阶段的下一个任务就是计算并保存每个被压缩区域中活动数据的首字节在压缩后的新位置。需要注意 的是:汇总阶段在目前被实现为一个串行阶段,这也是“准”并行方式的由来;并行实现也是可能的,只是与标记和压缩阶段

29、的并行化相比,它对性能的影响不大。在压缩阶段,垃圾收集线程使用汇总数据确定需要被填充的区域,然后它们就可以独立地把对象拷贝到这些区域中而不再需要额外的同步。这就产生了一个堆,堆空间的一端塞满了活动对象,而另一端则是一个单一而连续的空闲内存块。)何时使用并行压缩收集器?和并行收集器一样,并行压缩收集器同样有益于在多机器上运行的应用程序。除此之外,年长代收集的并行化操作方式还减少了停顿时间,使得并行压缩 收集器比并行收集器更为适合那些有停顿时间限制的应用。不过,对于运行在大型共享主机(如SunRays)上的应用来说,并行压缩收集器也许并不太合适, 因为任何单一应用都不应长时间独占几个。在这样的机器

30、上,要么考虑通过命令行选项“-XX:ParallelGCThreads=n”减少垃圾收集 线程的数目,要么考虑选择一种不同的收集器。)并行压缩收集器的选用并行压缩收集器只能通过命令行选项“-XX:+UseParallelOldGC”进行显示的选用。八、并发的标记-清理收集器(Concurrent Mark-Sweep(CMS) Collector)对于许多应用来说,端到端的吞吐量并不象响应时间那么重要。通常来讲,对年幼代的收集并不会引起太长时间的停顿。但是对年长代的收集,虽然不常发生,却可能导致停顿时间过长的状况,在堆空间很大时尤其明显。为了解决这个问题,HotSpot JVM包含了一个名叫“

31、并发的标记清理()收集器”的收集器,它也被称为低延迟收集器。)收集器如何收集年幼代?同并行收集器,不再赘述。)收集器如何收集年长代?采用收集器收集年长代时,大部分收集任务与应用程序并发执行。收集器的收集周期始于初始标记,它采用串行、方式进行,用于确定根引用集。随后进入并发标记阶段,完成对所有活动对象的追踪和标记,在 中该阶段已开始采用并行、并发方式进行。由于在并发标记过程中应用程序正在执行并可能更新了一些对象的引用域,因此并发标记过程结束时并非所有活 动对象都已确保被标记出来。为了处理这种情况,应用程序再次暂停,收集过程进入再标记阶段;它采用并行、方式进行,通过对并发标记过程中被修改对象 的再

32、次访问最终完成整个标记过程。因为再标记阶段的停顿时间往往是最长的(超过初始标记停顿甚至次收集停顿),因此再标记过程会尽量采用并行方式进行。再标记阶段完成后,所有活动对象都已确保被标记,随后进入并发清理阶段,它采用串行、并发方式进行,就地回收所有垃圾对象。下图展示了串行的标记-清理-压缩收集器和收集器在收集年长代时的区别:因为一些任务(例如再标记过程中对被修改对象的再次访问)增加了收集器的工作量,收集器的总体开销自然会增大。对于大多数试图减少停顿时间的收集器来说,这是一种典型的折衷。收集器是唯一一个不使用压缩技术的收集器。也就是说,在垃圾对象所占用的空间被释放以后,收集器并不把活动对象全部推挤到

33、代空间的某一端去。见下图:这 种方式节省了回收时间,但却因为空闲空间不再连续,收集器也就不再可能只使用一个简单指针即可指示出可分配给新对象的下一个空闲空间的位置,相反,它现在 需要使用空闲空间列表。也就是说,收集器创建并通过一组列表把内存中尚未分配的区域连接起来,每当有对象需要分配空间时,适当的列表(基于所需内存数量) 被搜索,以找到一块足以放下该对象的空闲区域。作为结果,与使用“bump-the-pointer”技术时相比,年长代中的分配操作变得更加昂贵。同时 这也给年幼代收集带来了额外的开销,因为在其收集过程中每提升一个对象都会触发一次年长代中的分配操作。收集器的另一个缺点是和其他收集器相

34、比它需要更大的堆空间。一方面,由于在标记阶段应用程序被允许运行,它就可能继续分配内存,从而可能使年长 代空间不断地增长;另一方面,虽然标记阶段完成后所有活动对象都已确保被标记,但是在标记过程中一些对象却可能变为垃圾对象,而且直到下次年长代收集之前 它们不会被回收。这样的对象也被称为游浮垃圾。收集器的最后一个缺点是由于缺乏压缩它可能引发碎片化问题。为了对付碎片化,收集器跟踪对象的流行尺寸,预估未来需求,并为满足需求还可能分割或合并空闲内存块。不象其他收集器,收集器并不是等到年长代填满后才启动对年长代的收集,而是尝试尽早启动年长代收集,以便在年长代被填满之前收集过程可以完成。 否则的话,收集器将重

35、新采用在串行和并行收集器中使用的标记清理压缩算法,尽管该算法工作于方式,也更加耗时。为避免这种情况的发 生,收集器对先前收集所耗时间和代空间充满所耗时间进行统计,并据此确定收集启动时间。另外,当年长代的空间占用率超过启动占用率 (initiating occupancy)时,收集器也将启动一次收集。启动占用率的值可以通过命令行选项“-XX:CMSInitiatingOccupancyFraction=n”进行设定,其中 n 表示年长代空间大小的百分比。缺省值为。总的来说,与并行收集器相比,收集器(有时甚至显著地)减少了年长代收集的停顿时间,而代价是略有增加的年幼代收集的停顿时间、吞吐量方面的一

36、些损失和额外的堆空间需求。)增量模式收集器可以采用让并发阶段增量完成的模式运行。这种模式通过对并发阶段的周期性暂停把处理能力交还给应用程序,以减少并发阶段持续时间过长所带 来的不利影响。收集工作被分成许多小的时间块,它们在年幼代收集的间歇期被调度。当应用程序既需要收集器提供的低停顿时间,又只能在很少的 (比如说到个)上运行时,这个特性就相当有用。)何时使用收集器?如果应用程序需要更短的垃圾收集停顿时间并且能够承担在运行时和垃圾收集器共享处理器资源,那么就可以使用收集器。(由于其并发性,在垃圾收集 过程中收集器将和应用程序抢夺周期。)通常来说,具有较大的长寿数据集并且运行在个或多个上的应用程序,

37、更容易受益于收集器的 使用。一个典型的例子就是服务器。对于任何需要低停顿时间的应用程序来说,收集器都值得考虑。对于年长代尺寸适中并且运行在单一处理器上的交 互式应用程序来说,使用收集器同样可能取得不错的效果。)收集器的选用收集器只能通过命令行选项“-XX:+UseConcMarkSweepGC”进行显示的选用。如果希望收集器在增量模式下运行,还需要通过命令行选项“-XX:+CMSIncrementalMode”启用该模式。九、收集器、堆尺寸和虚拟机的自动选择在J2SE 5.0中,根据应用程序所运行的硬件平台和操作系统,垃圾收集器、堆尺寸和HotSpot虚拟机(客户机或服务器)的缺省值被自动选定

38、。这些自动的选择不仅减少了对命令行选项的使用,而且还更好的满足了不同类型应用程序的需要。)服务器级硬件(server-class machine,不使用“机器”这个词是因为读起来太拗口)的定义:服务器级硬件必须同时满足以下两个条件:拥有个或以上的物理处理器拥有或以上的物理内存该定义适用于所有平台,位Windows平台除外。)服务器级硬件与非服务器级硬件下各项缺省值的比较:机器类型虚拟机垃圾收集器堆尺寸初始值-Xms堆尺寸最大值-Xmx服务器级硬件服务器版并行收集器物理内存的1/64,不超过物理内存的1/4,不超过非服务器级硬件客户机版串行收集器注意:本节所涉及的堆尺寸指的是Java堆的大小,包

39、括年幼代和年长代,但不包括永久代。 本文以J2SE 5.0为蓝本,从应用的角度出发,对HotSpot JVM中包含的四种垃圾收集器及相关概念作了全面的介绍,为更好的理解和掌握Java中的垃圾收集子系统打下了坚实的基础。有关HotSpot JVM中垃圾收集性能调整的问题,以后再作专门的介绍。JVM HotSpot2010年01月03日 星期日 上午 12:31目的:本文描述了Sun公司的HotSpot Java虚拟机的垃圾收集工作原理。以便为更多Java爱好者在设计,开发以及部署时带来更多便利和益处。摘要:JVM规范中要求任何实现JVM的实现必须要提供一个能够回收未被使用内存的机制。这个机制就是

40、垃圾回收(GCGarbage Collection)。然而垃圾回收机制设计的好坏将直接影响依赖其运行的java应用的性能(包括处理能力,响应时间等)。在接下来的章节中将详细介绍SUN公司的Java虚拟机(其正式名称是Sun HotSopt JVM)中的垃圾回收机制。分代垃圾回收HotSpot JVM使用分代垃圾回收的方式。这种垃圾回收方式并不是HotSpot JVM的首创,而是人们在实践中发现存在下面的两条规律并在很早的时候就提出来了。即:1)大多数对象在创建后很短的时间内就会没有任何对象再使用它了,即未被其它对象引用。2)大多数一直被使用的对象(老对象)很少引用新创建的对象。对于Java应用

41、来说这两条规律始终是存在的,也有人根据这些规律称其为“弱分代”。因为将java对象分为“年轻”对象和“老”对象时并没有一个非常明确的指标而是由JVM规范的实现者控制的(当然JVM也可以提供参数让具体的开发者设定)。在HotSpot JVM中将分配到的内存堆(Heap)分为两个物理区域,一个是“年轻”区,另一个是“老”区。在这里我将“年轻”的一代叫做“新生代”,而对应的将“老”的一代对象叫做“老生代”。l 新 生代:绝大多数新创建的对象存放于此,这个区域一般来说比较小而且垃圾收集的频率也比较高。因为许多对象在创建后很快就会“死”去,在每次的“新生代”垃 圾收集后能够“幸存”的对象非常的少。使用在

42、“新生代”的这种垃圾收集叫做次要垃圾收集(Minor Collection)。正是因为“新生代”的大小(较小,寻址时间很短)和其存放的对象的特点(寿命短,所以有很多垃圾,每次收集都能释放较大的内存空 间)使得次要垃圾收集的效率非常高。见图1。图1:分代垃圾回收l 老 生代:在“新生代”中生存了较长时间的对象将被提升为“老”对象并转移到“老生代”区。这个区域一般要大一些而且增长的速度相对于“新生代”要慢一些,所 以负责“老生代”垃圾收集的主垃圾收集(Major Collection)的执行频率与次要垃圾收集比要低很多。主垃圾收集发生在“老生代”中的对象占用的存储空间达到一定的量值的时候。见图1。

43、*为方便起见,在下面的章节中将用次收集来替代“次要垃圾收集”,用主收集替代“主要垃圾收集”。为了使次收集的收集时间尽可能的短,HotSpot JVM使用了一种叫做卡表(Card Table)的机制见图2,来避免在每次进行次收集的时候遍历整个“老生代”。图2:HotSpot中的卡表卡表的机制是将“老生代”以512字节为单位进行划分,划分得到的每个区域叫做一个卡。每个卡在卡表中有占用一个一个字节的标识。java代码在执行的过程中JVM一旦发现“老生代”的对象引用了或者释放了“新生代”中的对象,那么JVM就要将与之对应的卡表中的状态置为相应的值。这样在次收集的时候只遍历被标记为“脏”的卡,以便知道哪

44、些“新生代”的对象被引用中,是不可以进行回收的。分代进行垃圾收集的好处是可以根据每个代的具体特点为其设定不同的垃圾收集算法。在新生代中往往使用速度较快的垃圾收集算法,因为次要收集的频率比较高。这种算法在内存的使用效率上没有优势,好在“新生代”的空间占整个JVM内存堆比例较小,尚不能对性能构成大的问题。而内存使用效率高的算法往往用在“老生代”的垃圾收集上。因为“老生代”占据着JVM堆的很大部分。虽然“老生代”中进行的主收集每次的收集时间相对于次收集要长好多,但是主收集在频率上要比次收集少很多,故对性能的影响也不大。正是这种新、“老生代”的相互补很好的平衡JVM垃圾收集中的瓶颈。新生代的组成“新生

45、代”又3个部分组成,见图3。一个Eden和两个生存区(Survivor Space),图3:新生代组成其中:l Eden:绝大部分新创建的对象存放在此区域。为什么说绝大多数而不是所有的呢?原因是应用在创建一个非常大的对象的时候JVM会直接将其分配在老生代而非新生代。在每次完成次收集的时候Eden区域总是空的。l 存活区:顾名思义在垃圾收集过程中没有被当作垃圾收集的对象将放在次区域中。也就是说这个区域中的对象至少经历了一次次收集。存放存活区的对象在被“提升”到老生代前还有机会被收集。存活区有一对,他们中的一个始终保持为空,另一个用于存放存活下来的对象。图4描述了次收集的收集过程,其中绿色部分是未

46、被使用的对象图4:一次次收集(即垃圾)。从图中可以看到在Eden区的绿色部分将被收集而幸存下来的对象 (白色部分)将被移到没有被使用的存活区2。在存活区1中的绿色部分是也是不被使用的对象,这些对象也将被收集。而位于存活区1中的蓝色部分是尚被引用但 是还不够“老”的这些对象也将移到到存活区2。存活区1中剩余的部分就是被引用且已经够“老”的对象,他们将被移到老生代区。在完成了一次次收集后(见图5),两个存活区就会交互角色。即存活区2中将存放存活对象,存活区1将不被使用。Eden区将会变的空空如也。同时由于有新对象移到了老生代,老生代的空间将被更多的对象占据。图5:完成一次次收集后垃圾收集器Sun 的HotSpot

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论