1引言 html
一个健壮的 Java™2平台,Standard Edition (J2SE™)拥有一个自动内存管理机制,它为开发者们屏蔽了复杂的内存管理步骤。 前端
本文提供了一个关于java Hotspot 虚拟机中内存管理机制的简单概述,它描述了一个可用于垃圾回收的内存管理器,而且提供了关于选择和配置一个回收器以及设置内存区域大小的回收操做。它一样能够做为一个参考书,本文列举了与垃圾回收器行为相关的一些最经常使用的方法,而且描述了他们之间千丝万缕的关系。 java
第二章节中,咱们为读者展现最新的自动内存管理器的概念,在那里咱们将讨论手动为数据分配内存空间对于程序员的好处。 linux
第三章节中,咱们将简述一般的垃圾回收器的概念,设计的选择,以及性能指标。本章节中,咱们还引入了称之为“代”的,一个经常使用的内存组织方式,它将基于对象的预期寿命来将不一样的对象划分到不一样的区域中。这种按照代来划分的方式已经被证实了可以有效地减小垃圾回收器的暂停时间和让每个应用拥有一个良好的性价比。 程序员
在剩余的章节中,咱们将会提供HotSpot的一些信息。在第四章节中,咱们将描述 javaSE 5.0 update 6中已经存在的4种垃圾回收器,和他们的内存管理方式,还总结了每一种类型的垃圾回收器的最佳应用场合。 web
第五章节描述了javaSE 5.0 如何为基于操做系统的,运行于虚拟机上的应用程序自动选择垃圾回收器,设置堆大小,以及动态内存回收如何知足用户的指望。咱们称这个技术为人体工程学。 算法
第六章提供了配置和选择一个垃圾回收器的建议,而且提供了一些处理OutOfMemoryError异常的方法。 编程
第七章简单介绍了一些用来评估垃圾回收器性能的工具。 windows
第八章列出了一些涉及到垃圾回收器的选择,和行为的经常使用的命令行选项。 数组
最后的第九章提供了一些本文涉及到的文档的连接。
2手动VS自动内存管理
在内存管理过程当中,咱们认识到当咱们分配的对象已经再也不被使用的时候,咱们就要释放这个对象所占用的内存空间,(标记这块内存区域)为后续的内存分配让出空间。在一些编程语言中,内存管理是程序员的职责(由程序员本身来管理内存的使用),可是因为这个任务的复杂性会致使许多常见的错误,可能会致使意外或错误的程序行为以及崩溃。基于以上的缘由,大部分的开发时间每每都耗费在调试和纠正这种错误上。
在手动管理内存的过程当中,一个空悬的引用一般会形成这样的问题。当这个对象占用的内存空间已经被释放掉的时候,其余的对象可能还拥有这个对象的引用。若是其余的对象试图访问原始的对象,可是原来的空间已经被分配给新的对象,那么结果就是不可预知的。
手动内存管理还存在另外一个常见的问题是内存泄漏,这些泄漏发生在内存已经被分配并再也不被引用可是却没有被释放的时候。例如,若是你打算释放一个链表所引用的全部空间,可是你错误地只是释放了链表的第一个元素,剩下的列表元素虽然再也不被引用,可是他们却离开了整个程序的控制范围,他们所占用的内存,既不可能被再次使用也不可能被回收。内存泄漏发生的时候,程序能够继续运行,直到耗尽全部可用的内存。
一种称之为垃圾回收器的自动内存管理程序目前已经成为大多数现代面向对象的语言用来替代以前的内存管理方式。自动内存管理器提高了代码的抽象性和接口性以及可靠性。
垃圾回收器避免了引用悬空的问题,由于一个被引用的对象将永远不会被垃圾回收器收回,因此他所占用的内存就不会被视为空闲区。垃圾回收器也解决了内存泄漏的问题,由于它会自动释放全部再也不被引用的对象所占用的内存。
3垃圾回收器的概念
一个垃圾回收器的职责有:
分配内存
确保任何被引用的对象保留在内存中
释放全部在执行的代码中不可达的引用所指向的对象所使用的内存
咱们称一个被引用的对象称之为存活的对象,一个再也不被引用的对象称之为死亡对象,并标记为垃圾。一个为对象寻找和释放其所占用的内存空间的过程,咱们称之为垃圾回收。
垃圾回收器只能解决一部分而不是全部的内存分配问题。例如,你能够建立一个对象,无限期地引用他们,直到没有更多可用的内存。垃圾回收器自己也是一个既占用时间也消耗资源的复杂任务。
垃圾回收器处理着一个用于组织内存的分配和释放空间的精确的算法,而且向程序员屏蔽掉这一个过程。空间一般来自于一个称之为堆的内存池中。
垃圾收集的时间由垃圾收集器决定,一般状况下,当整个堆或者他的子集被填满或达到了某个百分比占用的阀值的时候会发生垃圾收集事件。
为了分配请求的任务,寻找一块具备必定规模的未使用是的内存块是一个难题,最经常使用的内存分配算法的主要问题是避免内存碎片,同时保持高效地分配和释放。
理想的垃圾收集器的特色
一个垃圾回收器应当是既安全又全面的。这意味着,存活的数据永远不会被错误的释放,
而且垃圾不该超过必定收集周期以后仍然没有被收集。同时也意味着垃圾回收器应该是高效的,不该当使用应用程序的暂停来完成他的垃圾回收工做。
然而,在大多数实时操做系统中,时间,空间和频率每每是相互取舍的,举个例子,若是堆空间太小,收集工做会更快,可是堆也会被迅速地填满,所以须要更频繁的收集操做,相反地,一个足够大的堆空间,须要更长的时间来填满,所以收集频率会更低,可是收集工做会耗费更多的时间。
理想的垃圾回收器另外一个的特色是碎片的限制。当内存上的垃圾对象被释放的时候,释放出的空闲区域可能会是一小块一小块的不连续空间,这样会致使任何一个连续的区域中没有一个区域有足够的空间用于分配一个大的对象。有一种消除碎片的方法称之为压缩,咱们将会在下面的各类垃圾回收器的设计中讨论到。
可扩展性也很重要,在多处理器的操做系统上,分配操做不该当成为多线程应用程序的瓶颈,收集操做也是如此。
设计选择
当咱们设计或者选择一个垃圾回收器算法的时候,咱们必须从如下选项中做出一个选择。
串行或者并行:
在串行回收中,同一时间只能有一件事情发生。例如,即便当多个CPU能够用的时候,也只有其中的一个用于执行回收操做。当咱们使用并行收集的时候,垃圾回收任务能够分割成几个部分,在不一样的CPU中,同时并行执行这些子部分。并发操做能够更迅速地完成收集到错,不惜牺牲掉一些额外的复杂性和潜在的碎片。
并发或者停掉整个世界:
当经过“中止世界”的方式来运行垃圾回收器,垃圾回收器会在收集过程当中暂停应用程序。另外,一个或者多个垃圾回收器任务能够并发执行,就是说能够与应用程序一块儿同时运行。一般状况下,一个并发的垃圾回收器能够并发执行大多数的工做,然是同时也可能偶尔须要作短时的“中止世界”的暂停操做。“中止世界”的垃圾回收器是简单的并发回收器,由于堆在收集过程当中是被冻结且对象不能改变的,可是他的缺点是,咱们可能不但愿某一些应用程序在运行过程当中被暂停。相应的,暂停时间较短的并发进行垃圾收集器,可是收集器必须格外当心,由于在同一时间,应用程序更新操做的对象和垃圾收集器操做的对象可能正在同时进行。这样会增长并发回收器的运行开销 ,这会影响到收集器的性能和须要更大的堆。
压缩 VS 不压缩 VS 拷贝:
在垃圾回收器已经决定哪些内存中的对象是活着的,哪些是垃圾以后,他就能够开始压缩内存了,移动全部存活的对象到一块儿(移动到一个连续的空间中),而且彻底回收其他的内存。压缩以后,咱们能够很是容易和快速地将一个新的对象分配在第一个空闲的内存位置上,而且利用一个简单的指针来跟踪对象的下一个位置来做为下一次分配的起始地点。与压缩收集器相反,非压缩收集器释放垃圾对象所占用的内存空间以后并不会像压缩收集器同样移动全部的存活对象来释放一块连续的空闲区域。这样作的好处是能够更快地完成垃圾收集,可是缺点是会形成潜在的碎片。一般来讲,从内存就地释放的堆中分配内存比已经压缩过的堆中分配内存更昂贵。由于它必须从堆中搜索出一块连续而且足够大的内存来容纳新的对象。
第三种方法就是拷贝回收器,他复制或者调整活着的对象到另外一个内存区域中,它的好处就是对象所在的旧内存区域能够被认为是空闲的内存区域,能够很是快速和容易地进行后续的分配,但缺点就是拷贝旧内存区域中的对象到新的区域中须要额外的时间和空间。
性能指标:
利用几个指标来估量垃圾回收器的性能,包括:
吞吐量—在一段足够长的时间内,减去垃圾回收器时间后剩余的时间与所占整体时间的比例(不包含垃圾回收器的时间)
垃圾回收器的开销—吞吐量的补数,也就是说,垃圾回收器所占时间与总时间的比例。
暂停时间—应用程序执行过程当中,垃圾回收器执行时,应用程序暂定的时间。
回收效率--相对于应用程序的执行,回收操做发生的次数。
足迹—大小的度量单位,如堆大小。
迅速--一个对象变成垃圾和内存变成可用之间的时间
一个交互的应用程序可能须要短暂的暂停时间,然而对于非交互性的应用程序,总体的执行时间将会更重要。一个实时的应用程序会要求在任什么时候期,垃圾回收器暂停时间和花费在收集过程当中的时间比例都要有一个最小的上限。一个足够小的空间会成为一个运行在小型我的电脑或者嵌入式系统的主要关注点。
代回收
当一个被称之为代回收的技术被使用的时候,内存将会根据代来划分,也就是说,将不一样年龄的对象划分到不一样的对象池中。例如,最经常使用的配置含有两代,一个是年轻代,一个是老旧代。
在执行垃圾回收的时候,不一样的代会使用不一样的算法,不一样的算法是基于不一样代的特色进行优化的。一般的垃圾回收器会利用这些观察结果,好比弱代假说,它被应用于许多编程语言编写的应用程序,包含java编程语言:
大部分配给对象的内存,不会被长时间的引用(既不会存活好久),这意味着,这些对象 是英年早逝的。
不多存在从老一代到新一代的引用。
年轻代的垃圾回收会相对频繁、高效、快速,这是由于年轻代的内存空间一般比较小而且被认为其中的大部分对象不会存在一个长时间的引用。
一些年轻代中的对象会存活下来,最终促进收集器将他们放入到老一代当中。见图一,老一代的回收器比年轻代的大而且增加相对缓慢得多,所以,老一带的回收行为比较难以发生,可是一旦发生了,就会耗费比较长的时间来完成。
为年轻代选择的垃圾回收器算法一般是速度优先的,由于年轻代的回收速度很是频繁。另外一方面,老一代的回收器一般使用更节省内存的算法,由于老一代占据了大部分的堆而且老一代算法必须在垃圾密度低状况下的工做得很好。
4 Java SE 5.0 HotSpot JVM 的垃圾回收器
java HotSport 虚拟机包含了4种垃圾回收器,好比java SE 5.0 update 6.这些垃圾回收器都是基于代垃圾回收器的。在这一章节中,咱们将讨论不一样类型的代回收器,而且讨论为何对象分配一般是快速和高效的。下面会提供每一种收集器的详细信息。
HotSpot的代划分
Java HotSpot 虚拟机中的内存,被分配到三个代中:一个年轻代,一个老旧代,一个永久代。大部分的对象将被初始化并分配在年轻代中,老旧代中包含的对象来自于部分存活的年轻代,还有一些大的对象也会被直接分配到老一代中。永久代持有的对象是方便于垃圾回收器管理的对象,好比类和方法的描述,以及本身的类和方法。
年轻代包含一个被称之为Eden的空间和两个较小的幸存空间,如图2,大部分的对象都被初始化分配在Eden区域中(正如前面所说,一些比较大的对象可能会直接分配在老一代),幸存空间至少保持一个年轻代收集器运行时候,幸存下来的对象,这些对象被赋予更多的死亡机会以前,被认为“足够老”,则会被晋升到老一代。在任何给定的时间汇总,生存空间中的一个保持着全部“幸存”的对象,而另外一个则是空置的,直到下一次回收器开始工做。
垃圾回收器的类型
当年轻代的空间被填满的时候,年轻代的回收器(有时被称为次要的回收器)就会在此时开始工做。当老一代或者永久代的空间被填满的时候,全部的垃圾回收器就会开始工做,这就是说,全部代上的回收器都会开始工做。,通常状况下,年轻代的垃圾回收器会先开始工做,
使用的收集算法是专门为年轻代进行设计的算法,由于年轻代须要一个最高效的算法来识别年轻代中的垃圾对象。而后,老一代和永久代上的垃圾回收器将根据为他们设计的算法开始进行工做。若是选择了压缩空间,那么每一代上的空间都将被压缩。
有的时候老一代的空间太满以致于不能接受来自于年轻代中转移过来的全部对象,特别是年轻代的回收器是优先工做的时候。在这种状况下,除开CMS以外的全部回收器,在年轻代上的回收算法都被中止执行,做为替代,老一代的回收器算法一般会在整个堆中执行(老一代上的CMS回收器是一个特殊的回收器,由于他没法在年轻代上展开回收工做)。
快速分配
正如你所看到的以下所述的垃圾回收器,在多数状况下,内存上存在着大量连续的内存块来分配给对象。使用一个简单的凹凸指针技术在这些块上分配对象是高效的,这就是说始终保持跟踪在以前分配的对象后面。当一个新的分配请求须要执行的时候,全部须要作的事情就是检查代上剩余的内存是否知足须要分配的对象的要求,若是知足,则更新指针的位置,而且初始化对象。
对于多线程的应用程序,分配操做必须是线程安全的。若是使用一个全局锁来保证这一件事,那么在代上分配空间将成为一个瓶颈,而且下降性能。为了解决这个问题,HotSpot JVM 使用一个称之为 线程本地分配缓存(Thread-Local- Allocation Buffer)的技术,它给每个线程提供一个独有的缓存(一小部分的代空间),来提高多线程分配的吞吐量。因为每个TLAB上只有一个线程进行分配操做,分配操做能够利用凹凸指针,无需使用任何锁定,使操做迅速到位。只有在少数状况下,当一个线程的的TLAB填满和须要一个新的TLAB的时候,必须使用同步来予以确认。因为使用了TLAB技术,使得(JVM)最大限度地减小了内存浪费。例如,由选择器分配大小的TLAB,平均浪费的空间小于Eden的1%。结合使用TALB和 基于凹凸指针的线性分配技术,使整个分配生效,仅仅只须要大约10个本地指令。
串行回收
使用串行回收器,在“暂停世界”策略中,年轻代和老旧代之间的操做是串行完成的(使用一个单一的CPU)。这意味着,回收器工做的时候,应用程序应当中止执行。
使用串行回收器的年轻代
图3展现了一个使用串行回收器的年轻代的工做过程。Eden中存活的对象被复制到一个刚初始化并且空着的幸存空间(图中的To 标签),除非那些太大的对象而没法放到To空间中,这样的对象直接复制到老一代的空间之中。那些已经在幸存空间中占用空间的还活着的对象(from 标签)还相对年轻的话将被拷贝到另外一个幸存空间中,当这些活着的对象相对老的话则拷贝到老一代的空间中。注:若是To空间已经变满,那些来自Eden和From的活对象将不会被拷贝到To中而是放入年老区中,无论他们在多少次的年轻代回收操做中幸存下来。任何剩余在Eden 或者 From 空间的对象在活着的对象被拷贝以后,根据定义他们都是“死”对象,咱们不须要去检查他们。(在图中垃圾回收器会给他们打上叉标记,虽然在事实上回收器不会去检查和标记这些对象)。
在年轻代的垃圾回收器结束以后,Eden 和 From空间已经清空,仅仅是To空间中包含活着的对象,在这一点上,幸存空间充当着一个交换空间的角色。
老旧代上的串行垃圾回收器
老旧代和永久代上的串行垃圾回收器使用一种叫 标记-清理-压缩(mark-sweep-compact)的算法。在标记阶段,收集器识别哪些对象是存活的。清理阶段,回收器将扫描整个代上的空间,识别出垃圾对象。随后,回收器执行滑动压缩,移动或者的对象前进到老旧代空间的前面部分(永久代也是如此),在空间的尾部留下一块连续的空间块。如图5,压缩操做使得老旧代和永久代上,使用凹凸指针就能快速地完成分配操做。
何时使用串行回收
串行回收器是大多数运行在客户端的机器上的应用程序的选择,并且他们没有低暂停时间的要求。在今天的硬件中,串行回收器能够很是高效地管理大多数拥有64MB堆空间和全收集最坏状况下的暂停时间少于半秒 的重要应用程序。
串行回收器的选择
在j2SE5.0版本中,串行回收器会被没有指名“server-class”的机器自动选择做为默认的垃圾回收器,这将会在第五章节中说到。在其余的机器上,串行回收器赞成经过使用 -XX:+UseSerialGC命令行选项来明确使用。
并行回收器
在今天,许多java 应用程序运行在拥有足够多的内存和多核心的cpu机器上,并行回收器,也叫吞吐回收器,被设计用来发挥多个cpu的特色来承担单个CPU中垃圾回收器的工做。
使用并行回收器的年轻代
年轻代上的并行回收器使用的并行算法借鉴了串行回收器。它仍然是一个使用了“中止世界”和复制的回收器,可是年轻代上的并行回收器在执行过程当中使用到了多个cpu,下降了垃圾回收器的时间上限,而且增长了应用程序的吞吐量。图6展现了年轻代上串行回收器和并行回收器之间的不一样之处。
使用并行回收器的老旧代
老旧代上的并行垃圾回收器使用和串行回收器一致的 标记-清理-压缩(mark-sweep-compact) 算法。
何时采用并行选择器
并行回收器适合那些运行在拥有多个CPU的机器上且对暂停时间没有限制的应用程序,虽然这种状况不多,可是有可能会很长,老一代的回收器将仍然工做。那些合适使用并行回收器的应用程序的例子有,批处理,记帐,工资单,科学计算等等。
你应当考虑选择并行压缩回收器(在下面描述)来替代并行回收器,由于 form空间执行的并发回收操做是在多个代空间之间的,不只仅是在年轻空间。
并行空间的选择
在J2SE 5.0 发行版,在server-class机器上并行回收器是默认的垃圾回收器,在其余的机器中,你能够经过执行 -XX:+UseParallelGC 命令行选项来明确打开并行选择器
并行压缩选择器
并行压缩选择器是J2SE 5.0 update 6中引入的,它和并行选择器的区别是它在老旧代上的垃圾回收上采用了一个新的算法。注:最后,并行压缩选择器终将要替代并行选择器。
使用并行压缩算法的年轻代
年轻代上的并行压缩算法采用和年轻代上的并行算法是同样的。
使用并行压缩算法的老旧代
使用并行压缩算法的老旧代和永久代在回收时使用 “中止世界”,多数并发出如今滑动压缩上。选择器采起三个阶段。首先,每一代的空间在逻辑上划分为固定大小的区域。在标记阶段,存活对象的初始集合 应用程序代码中,可达的存活对象,在垃圾回收器的线程之间,而后全部的存活对象会在这个阶段中标记出来。当一个对象被标记为存活,它的所在的区域的数据将会被更新,更新的内容是这个对象的大小和位置。
总结阶段的操做是基于区域,而不是对象。因为在上一次的压缩中,每一代的左侧区域一般是密集的,包含着大部分存活的对象。一些能够被清空的密集区域上进行压缩操做是不值得的。因此总结阶段要作的第一件事是检查区域中的密度,从最左边的一个开始,直到它到达一个点,这个点所在的空间能够从其所在的区域中清空和那些空间中的右侧是值得压缩的区域。在这个点的左侧区域都打上密集的前缀,而且不会移动这些区域上的任何一个对象。这个点的右侧区域都将被压缩,消除全部的死亡空间。总结阶段统计而且存储每个区域的第一个存活对象的第一个字节的新位置。注:总结阶段当前被实现为串行阶段;并行是可行的,可是对于标记和压缩阶段并行不是性能的重要部分。
在压缩阶段,垃圾回收器线程使用总结阶段的数据来肯定被填充的区域,而后线程能够独立地拷贝数据到区域中。这会形成堆的前面是密集的对象,后面是空闲的块。
何时使用并行压缩回收器
在不止一个cpu的机器上运行的应用程序适合使用并行压缩回收器。在老旧区域上的并行操做下降暂定时间而且使得有暂停时间限制的应用程序上并行压缩的选择器比并行选择器更合适。并行压缩选择器不适合那些运行在大型共享机器上的应用程序,由于在那些机器上面没有任何一个单独的应用程序能够独占好几个CPU,这个会延长时间的周期。在这样的机器上,要么减小垃圾回收器使用的线程个数(使用–XX:ParallelGCThreads=n这个选项)要么选择别的回收器。
如何选择并行压缩回收器
若是你要使用并行压缩回收器,你必须明确地调用 -XX:+UseParallelOldGC 这个命令行选项。
并发 标记-清除(CMS)回收器
对于一些端对端的应用程序来讲,吞吐量并以是一个快速响应的一个重要因素。年轻代回收器一般不会形成一个长时间的暂停。然而老旧代回收器在少数状况下可能会致使一个长时间的暂停,特别是涉及到一个很大的堆的时候。为了解决这个问题,HotSpot JVM 包含了一个称之为 并发标记-清除(CMS)的回收器,也叫低延迟回收器。
使用CMS的年轻代回收器
CMS回收器在年轻代上的操做和并发回收器所作的同样。
使用CMS回收器的老旧代
应用程序上老旧代的大多数回收操做使用CMS回收器来并发完成。
CMS回收器的回收周期以一个称之为初始化标记的短暂暂停开始,它肯定在程序代码中可达的存活对象的集合。接着,在整个并发标记阶段回收器将根据这个集合,标记出全部或者的的对象。由于在并发标记阶段期间,应用程序会运行和更新引用,所以在并发标记阶段没法保证全部的存活对象都会被标记。为了处理这个问题,应用程序会再次暂停,称之为再标记,此时将访问和标记那些在并发标记阶段被更新的的对象。由于再标记的暂停比初始标记更可靠,这回提高多线程会并发执行的效率。
再标记阶段的末尾,堆中全部的存活对象能够保证都被标记了,因而随后的并发清除阶段将会回收全部被定义的垃圾。图7展现了老旧代上使用串行标记-清除和并行标记-清除回收器的不一样点。
由于一些任务,好比再标记阶段中再次访问对象,提高了回收器要完成的工做数量,这一样会提高了上限。这是一个大多数的回收器试图下降暂停时间的典型权衡。
CMS回收器是惟一一个不采用压缩的回收器。这意味着,在清空死亡对象占用的空间以后,它不会移动存活对象到老旧代的一端。如图8
这样会节约时间,可是由于空闲空间不是连续的,回收器将再也不能使用一个简单的指针来标明下一个待分配对象可使用的空闲位置。为了解决它,咱们如今要使用一个空闲空间的链表。也就是说,他须要建立一些指向内存中还没有分配区域的链表,每当须要为一个对象分配空间的时候,一个适当的链表(基于内存所需的数量)必须搜索一个足够大的区域来持有这个对象,正因如此,在老旧区域上分配空间是一个相对简单凹凸指针技术更昂贵的操做。这一样会额外提高年轻代上回收操做的上限,当来自年轻代上的对象提高到老旧代上的时候,这样的分配会大量地出现。
另外一个CMS回收器的缺点是,它须要一个比别的回收器更大的堆需求。考虑到应用程序会在标记阶段继续运行,它将继续分配内存,这会致使老旧代的持续增加。虽然收集器保证在标记阶段定义出全部的存活对象,在这个阶段一些对象会成为垃圾而且不能被再次利用直到下一次回收器工做,这些对象被成为 漂浮垃圾。
缺乏压缩过程,最终仍是会出现碎片。为了处理碎片,CMS回收器会指定一个经常使用的对象大小,估算出将来的需求,而且会分割或者合并空闲的内存块去符合需求。
与其余的回收器不一样,CMS回收器不会在老旧代的空间变满的时候开始回收工做。它试图在足够早的时间就开始回收工做,所以他能够在老旧代变满以前就完成回收工做。另外,CMS回收器减小了串行和并行压缩器使用的“暂停世界”,标记-清除-压缩算法的时间成本。为了不它,CMS回收器基于 统计以前回收操做的时间和老旧代开始被占用的时间。CMS回收器一样会在这个时候启动回收操做,当老旧代被占用的时间超过了一个称之为初始化占用时间的值。初始化占用时间能够经过–XX:CMSInitiatingOccupancyFraction=n 这个命令行选项来设置,n是老旧代对象大小的百分比,默认值是68.
简单总结下,和并行回收器相比,CMS回收器下降了老旧代的暂停时间,这有些戏剧性,由于它提高了年轻代的暂停时间,减小了部分吞吐量,须要额外的堆大小。
增量模式
在这个模式中CMS回收器能够在并发阶段更快速地完成。这个模式打算减小由长期并发阶段形成的影响,它采用了按期地暂停当前的并发阶段使得当前的工做被挂起,让出处理器来处理应用程序。它的工做是这样的,在年轻代回收操做工做之间,回收器将会根据预期分配到不一样的块中。当应用程序须要经过并发回收来获取一个短期暂停而又运行在小数量处理器的机器中时,这是颇有用的。更多关于使用这个模型的信息,参见第九章节“java5.0 虚拟机上的垃圾回收器调整”。
何时使用CMS回收器
若是你的应用程序须要一个短时暂停的垃圾回收器,而且可让垃圾回收器在应用程序运行过程当中分享处理器资源,那么就合适使用CMS回收器(因为它的并发性,CMS回收器在回收周期使用的cpu周期与应用程序无关)。通常状况下,应用程序拥有一个相对大的集合来存储长期存活的对象(一个足够大的老旧代),而且运行它的机器拥有两个或更多的处理器,经常趋向于使用这个回收器。好比web服务器,CMS垃圾回收器一般被那些须要短时暂停的需求的应用程序所采用。它一般也适合那些在单个处理器上拥有合适老旧代大小的交互式应用程序。
使用CMS 选择器
若是你打算是使用CMS选择器,你必须明确地使用-XX:+UseConcMarkSweepGC这个命令行选项,若是你打算让他运行在增加模式中,一样须要使用 -XX:+CMSIncrementalMode 选项
5自动调整-自动选择和行文调整
在J2SE 5.0发行版中,默认的垃圾回收器,堆大小和 Hotspot 虚拟机(无论是客户端,仍是服务器)都是根据应用程序所运行的平台和操做系统进行自动选择的。当不设置任何命令行选项的时候,自动选择的结果会更适合各类类型的应用程序。
另外,并行垃圾回收器添加了一个新的动态调整回收。依靠这个功能,只要用户指按期望的行为,垃圾回收器器动态调整每个区域的堆大小,来符合行为的需求。这种组合了依靠平台来默认选择和依靠预期行为垃圾回收器调整的东西被叫作人工智能。人工智能的目的就是为最小化命令行调整的JVM提供一个优异的性能。
回收器,堆大小和虚拟机的的自动选择
一个服务器类的机器将根据如下之一的条件进行定义
2个或2个以上物理处理器
2个或2个以上千兆字节的物理内存
这个服务器类机器的定义适用于全部平台,除开window操做系统上运行的32位平台。
在非服务器类的机器上,默认的JVM选项,垃圾回收器,和堆大小以下
客户端JVM
串行垃圾回收器
初始堆大小为4MB
最大堆大小为64MB
在服务器类服务器,JVM将永远使用服务器JVM直到你显示指定-client 命令行选项来使用客户端JVM,在一个服务器类机器上运行服务器JVM,默认的垃圾回收器是并行回收器,不然默认的回收器是串行回收器。
在一个服务器类机器上使用并行垃圾回收器来运行这些JVM(客户端JVM或者服务器JVM),堆的默认初始值和最大值以下
初始堆大小是物理内存的1/64 ,最可能是1GB。(注意:;最小的初始堆大小是32MB,由于一个服务器类的机器定义的是最少含有2GB的内存,而且2GB的 1/64 是32MB)
堆最大是物理内存的 1/4,最多1GB。
另外,这些默认值一样适用于 非服务器类机器(初始化4MB堆大小和最大64MB堆大小)。默认值能够经过命令行选项来重定义,相关选项在第8章给出。
调节并行回收器的默认行为
在j2SE5.0的发行版本中,添加了一个调节并行回收器的新方法,基于应用程序对于垃圾回收器的指望行文,使用命令行选项来指定这个指望的行为来达成最大暂定时间和应用程序吞吐量的目标。
最大暂停时间目标
最大暂停时间目标的指定使用下面的命令行选项
-XX:MaxGCPauseMillis=n
这个解释将对并行回收器的暂定时间进行提示,最大暂停时间是n秒或者少于n。并行回收器将调整堆大小和其余的垃圾回收相关的选项来试图保持垃圾回收器的暂停时间小于n秒。这个调整会致使垃圾回收器下降应用程序的全局吞吐量,在一些状况下预期的暂停时间目标可能没法被实现。
最大暂停时间的目标策略分别被运用在每个代上。通常地,若是目标没有被达成,代会变得更小来试图达成目标。默认最大暂停时间没有被设置。
吞吐目标
最大吞吐目标意思是依据花费在垃圾回收和花费在非垃圾回收器(代指应用程序时间)上的时间。这个目标能够经过如下命令行选项来明确指定
- XX:GCTimeRatio=n
垃圾回收器和应用程序时间的比值是
1 / (1 + n)
例如:-XX:GCTimeRatio=19 设置的目标是垃圾回收器占用全部时间的5%,默认的目标是1%(n=99)。这个花费在垃圾回收器上的时间,是指全部代上垃圾回收器的时间总和。若是目标的吞吐量没有被完成,代空间将会增加使得在 两次垃圾回收工做期间,应用程序的运行时间得以增加。,一个巨大的代空间须要更多的时间来填满。
足迹目标
当吞吐量和最大暂停时间的目标被完成,垃圾回收器会下降堆的大小直到目标没有被完成。目标的完成边界就是足迹的到达点。
目标的顺序
并行回收器以完成最小暂停时间为第一目标,只有在这个目标完成以后才会去追求吞吐量的目标,足迹目标只有在前两个目标被完成后才开始考虑。
6建议
建议这一章补充了以前的章节介绍了自动配置垃圾回收器,虚拟机和堆大小的选择,覆盖了绝大部分的应用程序的合理配置。所以,初始的垃圾回收器的选择和配置什么都没作。这就是说,不须要特别指定垃圾回收器,注:让系统基于平台和操做系统为你的应用程序进行自动选择。而后测试你的程序。若是它的性能是能够接受的,拥有足够高的吞吐量和足够低的暂停时间。你就不须要修改垃圾回收器的选项。
另外一方面,若是你的应用程序试图经过垃圾回收来获取高性能,那么你最优先作的事情就是思考根据你的应用程序和你的平台所提供的默认垃圾回收选择器是不是合适的。若是不合适,那么你要明确地选择一个你认为合适的选择器,而且查看你的平台是否兼容。
你可使用章节七给出的工具来测量和分析性能。基于工具给出的结果,你能够考虑修改选项,例如控制堆大小或者垃圾回收器的行为。章节八中将给出一些通用的指定选项。请注意:最合适的性能调节,应当是先测量,而后再调节。测量的测试你实际上使用的代码相关。另外,请勿过分优化,由于应用程序的数据集,硬件等等,甚至垃圾回收器的实现都会随着时间的改变而改变。
这一章节提供了关于选择垃圾回收器和指定堆大小的信息。而后提供了调节并行垃圾回收器的选项,和提供了一些关于处理 OutOfMemoryErrors的建议。
什么时候选择一个不一样的垃圾回收器
在章节四中,介绍个每一个回收器的适用情形。章节五描述了不一样的平台上串行回收器和并行回收器的默认选择。若是你的应用程序或者环境特性与默认的回收器的适用状况不一样,请使用如下的其中一个命令行选项来明确使用一个垃圾回收器。
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC
堆大小
章节五描述了默认的初始和最大堆大小。这些值符合大多数的应用程序,可是若是你的性能分析出现了问题(见章节七)或者出现了OutOfMemoryError(将会在下面进行描述)显示你的问题出如今某一个代或者全部的堆的大小上,你能够经过章节八中提供的命令行选项来修改你的大小。例如,默认的最大堆大小是64MB这个在非服务器类机器上一般过小了,于是你能够经过 -Xmx 来指定一个更大的值。除非你的问题与长的暂停时间相关,否则你能够试图使用全部可使用的内存。吞吐量与可使用的内存成正比。拥有足够的可利用内存是影响垃圾回收器性能的重要因素。
在明确了你可以为全部的堆提供多少的内存以后,你能够开始考虑调整不一样的代上的大小了。以下描述,让回收器自动和动态修改堆大小来完成这个行为。
调整并行回收器的策略
若是垃圾回收器的选择(自动或者明确使用)是并行回收器或者并行压缩回收器,那么开始而且指定一个知足应用程序的吞吐量目标(见章节五)。不要选择一个最大的堆大小除非你明白你须要一个比默认最大堆大小还大的堆。堆会自动增加或者收缩它的大小来支持选择的吞吐量目标。在初始化期间和改变应用程序的行为来符合指望的期间堆大小会有一个摆动。
若是堆大小增加到它的最大值,在绝大多数的状况下这意味着在当前的最大堆大小下吞吐量目标没法被实现。为应用程序设置最大的堆大小值来接近平台上全部的物理内存但不包含引交换分区。再一次运行这个应用程序。若是吞吐量目标仍然没有被实现,那么应用程序的目标运行时间对于该平台上的可用的内存来讲过高了。
若是吞吐量目标能够被实现,可是暂停的时间太长了,选择一个最大的暂停时间。选择一个最大的暂停时间意味着你的吞吐量目标将不会被实现,所以应当选择一个应用程序能够妥协的值。
堆大小会在垃圾回收器试图知足相互竞争的目标之间进行摇摆,即便应用程序达到一个稳定的状态。这之间的压力来自达到吞吐量目标(这将会须要一个更大的堆)与最大暂停时间和最小足迹(这将会须要一个更小的堆)。
如何处理OutOfMemoryError
一个许多开发者常常遇见的问题是,应用程序由于java.lang.OutOfMemoryError而终止。这个错误在没有足够的内存来为对象进行分配的时候抛出来。这就是说,垃圾回收器不能找到更多的空间来容纳这个新的对象,而且堆没法进一步扩大。一个OutOfMemoryError 并不能直接确认是内存泄露的问题。这个问题也许是一个配置问题,例如指定的堆大小(若是不指定的话则是默认的)不能知足应用程序的需求。
诊断OutOfMemoryError的第一步是检查全部的错误信息。在异常信息中,进一步的信息会在“java.lang.OutOfMemoryError”以后提供。这里有一些常见的例子包含了这些额外信息的内容、意义和处理方式。
Java heap space(java 堆空间)
这代表了一个对象没法在堆上分配。这个问题可能只是一个配置问题。你能够捕获到这个错误,例如,若是使用 -Xmx命令行选项来指明最大的堆大小(或者是默认值)没法知足应用程序的需求。他也可能代表一个再也不被使用的对象不被垃圾回收器回收,由于应用程序无心地保持了这些对象的引用。HAT工具(见章节七)能够用来观察全部的可达对象和明确哪个引用来保持哪个对象的存活。另外一个潜在的错误来源有多是在应用程序中过多地使用了 finalizers 以至于线程调用 finalizers 没法跟得上添加finalizers到队列的速度。Jconsole管理工具能够用来监控在销毁期间的对象的数目。
PermGen space(永久代空间)
这代表了永久代的空间已经满了。如以前的描述,这是JVM存储元数据的堆区域。若是一个应用程序加载一个很大数量的类,那么永久代会自动增加。你能够指明永久代的大小经过命令行选项 –XX:MaxPermSize=n ,n是指明的大小。
Requested array size exceeds VM limit(请求的数组大小超过虚拟机的限制)
这代表了应用程序试图分配一个数组空间大于了堆的大小。例如,若是一个应用程序试图分配一个512MB的数组,可是最大的堆大小只有256MB,那么这个错误将会被抛出。在大多数状况下,这个问题缘由多是堆大小过小或者是一个BUG致使了应用程序试图建立一个被计算错误的巨大数组。
在章节七中介绍的一些工具能够用来诊断 OutOfMemoryError 问题。在这些有用的工具中有一些是 堆分析工具(HAT Heap Analysis Tool),好比jconsole管理工具和使用-histo选项的jmap工具。
7评估垃圾回收器性能的工具
各类诊断和监测工具能够用来评估垃圾收集的性能,本章节提供了一些简要的概述来描述其中的一些工具,更多的信息请参见章节九中的“工具和故障排除”链接。
–XX:+PrintGCDetails 命令行选项
一个简单的用来获取回收器的初始信息的方法是指定 –XX:+PrintGCDetails 命令行选项。对于任意一个回收器,这个的输出信息包含了每个代在垃圾回收以前和以后存活的对象大小,每个代上的全部可用空间,回收器消耗的时间。
–XX:+PrintGCTimeStamps 命令行选项
这会输出每个回收器的启动时间戳,另外若是你使用–XX:+PrintGCDetails 命令行选项这些信息也会输出。这些时间戳会帮助你将回收器的日志和别的事件日志关联起来。
jmap
jmap是一个命令行工具,包含在Solaris操做系统环境和linux(不包含windows)的Java 开发工具集(JDK)中。它会打印出运行中的JVM或者核心文件的内存相关统计数据。在不使用任何命令行选项的状况下,它会打印出全部被加载的共享对象,与Solaris的pmap工具类似的输出。对于更多的明确信息,可使用 -heap,-histo,或 -permstat 选项。
-heap 选项用来获取一些信息包含了垃圾回收器的名字,具体的算法细节(例如 并行垃圾回收器使用的线程数量),堆的配置信息,和堆的简单使用状况。
-histo 选项能够用来获取堆上的类的直方图,对于每个类,它会打印出堆中该类的实例数量,这些对象所占用的单位为字节的内存总数,和全合格的类名。当你试图理解堆的占用状况的时候,这个直方图会颇有用。
配置永久代的大小对于应用程序来讲是很重要的,特别是动态加载一个很大数据量的类的时候(好比 java Server Pages(JSP)和web containers(web 容器))。若是一个应用程序加载了过多的类,那么将会抛出OutOfMemoryError。Jmap的 -permastat 选项能够用来获取永久代上的对象统计信息。
Jstat
Jstat工具在Hotspot JVM使用了一个构件时仪表盘来提供应用程序运行中消耗的资源和性能信息。当诊断性能问题的时候可使用这个工具,而且一些特殊的和堆大小以及垃圾回收器相关的问题也可使用它。他的其余选项能够输出有关垃圾回收器行为和各类代的容量和使用状况的统计信息。
HPROF:堆分析(Heap Profiler)
HPROF 是一个包含在JDK 5.0的简单分析代理。他是一个使用java虚拟机工具接口(Java Virtual Machine Tools Interface )的动态链接库接口。它会输出分析信息到一个文件或者套接字中,以ASCII 或者二进制的格式。这些工具能够进一步地使用一个前端分析工具来处理。
HPROF可以展现CPU的使用率,堆分配统计,和监控竞争配置。此外,它能够输出完整的堆垃圾和报告java虚拟机上全部的监控器和线程的状态。HPROF在分析新能,锁竞争,内存泄露和其余问题的时候颇有用。参见章节九HPROF文档的链接。
HAT:堆分析工具(Heap Analysis Tool)
堆分析工具(HAT)用来帮助调试无心的对象保留。这个术语用来描述一个再也不被须要的对象因为被一个存活的对象所引用而保持存活。HAT提供了一个方便的手段来浏览对象在堆中的快照。这个工具容许必定数量的查询,包含“向我提供全部从根集合到对象的引用路径”,参见章节九的HAT文档连接。
8垃圾收集相关关键选项
咱们可使用一些命令行参数选项来选择垃圾回收器,指定堆或者代上的大小,调整垃圾回收器的行为,和活的垃圾回收器的统计信息。这一章节展现了其中的最普遍使用的选项。对于关于多方面的有效选项的更多完整列表和详细信息,见章节九。注:指定的数量时以“m”或者“M”结尾,表明兆字节,“k”或者“K”结尾表明千字节,“g”或者”G”结尾表明千兆字节。
垃圾回收器的选择
选项 |
选择的垃圾回收器 |
–XX:+UseSerialGC |
Serial 串行 |
–XX:+UseParallelGC |
Parallel 并行 |
–XX:+UseParallelOldGC |
Parallel compacting 并行压缩 |
–XX:+UseConcMarkSweepGC |
Concurrent mark–sweep (CMS) 并发标记清除 |
垃圾回收器的统计
选项 |
描述 |
–XX:+PrintGC |
打印每个垃圾回收器的基础信息 |
–XX:+PrintGCDetails |
打印每个垃圾回收器更详细的信息 |
–XX:+PrintGCTimeStamps |
打印每个垃圾回收器开始事件的时间戳。使用–XX:+PrintGC和–XX:+PrintGCDetails 能够在每一次垃圾回收器开始的时候打印这些内容。 |
堆和代的大小
选项 |
默认值 |
描述 |
–Xmsn |
See Section 5 |
堆的初始大小,单位为字节, |
–Xmxn |
See Section 5 |
堆的最大大小,单位为字节 |
–XX:MinHeapFreeRatio=minimum and –XX:MaxHeapFreeRatio=maximum |
40 (min) 70 (max) |
空闲空间占总空间比例的目标。这会运用于任何一代上。例如,若是最小值是30,而且空闲空间占该代上的空间比例小于30%,那么这个代空间就会扩展直到知足30%的空闲空间。近似的,若是最大值是60而且自由空间的比例已经超过60%,代空间的大小就会收缩直到自由空间只占到60%。 |
–XX:NewSize=n |
平台相关 |
默认的年轻代上的初始大小,单位为字节 |
–XX:NewRatio=n |
2 on client JVM, 8 on server JVM |
年轻代和老旧代的比例。例如,若是n=3,那么比例就是1:3而且Eden空间和幸存(survivor)空间一共占年轻代和老旧代的总空间的 1/4。 |
–XX:SurvivorRatio=n |
32 |
幸存空间和 Eden空间的比例,例如,若是n=7,每个幸存空间就是年轻代的 1/9(不是 1/8由于幸存空间有两个) |
–XX:MaxPermSize=n |
平台相关 |
永久代的最大大小 |
并行和并行压缩回收器的选项
选项 |
默认值 |
描述 |
–XX:ParallelGCThreads=n |
CPU的个数 |
垃圾回收器的线程数 |
–XX:MaxGCPauseMillis=n |
没有默认值 |
指示回收器的暂停时间为n秒或者更少。 |
–XX:GCTimeRatio=n |
99 |
设置目标花费在垃圾回收器上的时间占总时间的 1/(1+n) |
CMS回收器的选项
选项 |
默认值 |
描述 |
–XX:+CMSIncrementalMode |
不启用 |
开启并发阶段的增加模式,按期地暂停当前的并发阶段,挂起它的工做,并让出处理器来为应用程序工做 |
–XX:+CMSIncrementalPacing |
不启用 |
容许基于应用程序的行为,在放弃处理器以前自动控制CMS回收器的工做量 |
–XX:ParallelGCThreads=n |
CPU的个数 |
年轻代上的并行回收器的线程个数,和老旧代上的并行部分的线程个数 |
9更多的信息
HotSpot 垃圾回收器和性能调整
Java HotSpot 虚拟机的垃圾回收器
(http://www.devx.com/Java/Article/21977)
在java 5.0虚拟机上调整垃圾回收器
(http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html)
自动调整
服务器类机器的断定
(http://java.sun.com/j2se/1.5.0/docs/guide/vm/server–class.html)
垃圾回收器的自动调整
(http://java.sun.com/j2se/1.5.0/docs/guide/vm/gc–ergonomics.html)
Java 5.0 虚拟机的自动调整
(http://java.sun.com/docs/hotspot/gc5.0/ergo5.html)
选项
Java HotSpot 虚拟机的选项
(http://java.sun.com/docs/hotspot/VMOptions.html)
Solaris 和 linux 选项
(http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html)
Windows选项
(http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/java.html)
工具和故障排除
JavaSE 5.0平台的问题查找和诊断入门
(http://java.sun.com/j2se/1.5/pdf/jdk50_ts_guide.pdf)
HPROF:一个javaSE 5.0的 堆和CPU 分析工具
(http://java.sun.com/developer/technicalArticles/Programming/HPROF.html)
Hat:堆分析工具
(https://hat.dev.java.net/)
内存释放
内存释放,线程,和java基础内存模型
(http://devresource.hp.com/drc/resources/jmemmodel/index.jsp)
如何处理java内存释放的内存保留问题
(http://www.devx.com/Java/Article/30192)
其余
J2SE 5.0 的发行日志
(http://java.sun.com/j2se/1.5.0/relnotes.html)
Java虚拟机
(http://java.sun.com/j2se/1.5.0/docs/guide/vm/index.html)
Sun java实时系统(java RTS)
(http://java.sun.com/j2se/realtime/index.jsp)
垃圾回收器的一些书籍:《垃圾回收器:自动动态内存管理算法》做者 Richard Jones 和Rafael Lins和 John Wiley 和 Sons,1996年