java 内存管理 —— 《Hotspot内存管理白皮书》

说明

  要学习Java或者任意一门技术,我以为最好的是从官网的资料开始学习。官网所给出的资料老是最权威最知道前因后果的。而Java中间,垃圾回收与内存管理是Java中很是重要的一部分。《Hotspot内存管理白皮书》是了解Java垃圾收集器最权威的文档。相比于其余的一些所谓翻译文章,本文的翻译更加准确,通顺和全面。在翻译的过程当中若是出现一些问题,若是出现问题或者表述不清楚的地方,能够直接在评论区评论。html

1.简介

   JavaTM 2 Platform, Standard Edition (J2SETM) 其中一个强大特性即是自动内存管理,从而对开发者屏蔽了复杂的显式内存管理。

  这篇论文针对在J2SE 5.0版本发布的JAVA Hotspot虚拟机,对它作出了简要概述。本论文描述了垃圾收集器如何执行内存管理,而且在对垃圾收集器的选择和配置、可操做的内存区域的大小设置给出了建议。本文也提供了一些连接,其中一部分连接列举了能够影响垃圾收集器行为的选项,另外一部分列举了更多详细的文档。

  文章的第二节为那些对于自动内存管理仍是新事物的用户而设立。它简明的讨论了与须要程序员显示的回收内存相比,自动内存管理的有何益处。文章第三节紧接着描述分代垃圾回收的概念,方案选择,性能指标。本节也介绍了一个叫做“分代”的通用内存区域划分的方法,它是基于对象生存时间的内存区域划分方法。对于大部分应用来讲,这种基于代的划分有效地减小了垃圾回收时的暂停时间和总体性能开销。

  本论文的剩余部分提供了Hotspot VM的 细节信息。第四部分描述了可用的四个垃圾回收器,包括在J2SE 5.0 update 6新出现的回收器,以及这些垃圾回收器都在使用的分代的内存划分方法。对任何一个收集器,第4部分对适合于他们各自的垃圾回收算法的类型和要求作出了概述。

  第五部分描述了在J2SE 5.0中发布的能够在根据运行中的应用程序的平台和操做系统中来系统地自动选择垃圾收集器(1),堆内存大小,HotSpot虚拟机(客户端模式或者服务器模式),以及动态垃圾回收(2)以便自适应用户自定义的行为的技术,这项技术被称为人体工程学。

  第六部分提供了选择和配置垃圾回收器的推荐规范。它也提供了关于OutOfMemoryErrors时该如何作的一些建议。第七部分简明的描述了评估垃圾收集性能时能够用到的一些工具。第八部分例举了有关垃圾收集器相关选项和行为的一些通用命令行。最终,第九部分提供了本论文所覆盖的观点的详细文档的连接。前端

2.显式VS自动内存管理

  内存管理是一个识别再也不使用的被分配的对象,从新分配(释放)被这些对象占用的内存,而且使这一系列的分配可用的过程。在一些程序语言中,内存管理是程序员的责任。这个复杂的任务形成了许多通用的可以形成异常和错误的程序执行行为以及崩溃。结果,大量的开发者把时间花费在调试和力图纠正这些错误上面。

  一个在显式内存管理编程上面常常出现的问题就是悬空引用。他是一个对象使用空间已经被从新分配可是其余对象仍在引用。若是一个对象试图去引用这个对象最初的引用,可是这个对象已经被分配到新的对象,这个结果是未知的而且不是被指望的。

  另一个显式内存管理的公共问题是内存空间泄露。泄露形成的缘由是对象再也不被引用可是并无被释放。举个例子,若是你打算释放一个链表占用的空间,可是你犯了一个只释放了这个链表头结点的错误,链表剩余的元素再也不引用但他们在程序中不可达,而且不能被从新使用或者从新覆盖。若是足够的泄露发生,他们能不断的消耗内存直到全部内存被耗尽。

  使用叫作垃圾回收器的程序来自动管理内存被认为是内存管理的一种通用替代方法,尤为是在如今大多数面向对象的语言中。自动内存管理增长了接口的抽象和代码的可靠性。

  垃圾收集器避免了悬空引用的问题。由于仍然被引用的对象永远不会被垃圾会收取回收因此确定不会被释放。垃圾收集器也解决了上面描述的内存空间泄漏的问题,由于它会自动释放因此再也不引用的内存。java

3.垃圾收集器的理念

  垃圾收集器有以下职责:

  1.分配内存

  2.确保任何被引用的对象仍然在内存中间

  3.从新覆盖那些在执行代码中引用不可达的内存。

  仍然被引用的对象叫作存活的对象。再也不引用的对象被认为是死亡的对象,术语上叫作垃圾。发现和释放(或者被称为再生)这些对象所占用对象的过程叫作垃圾回收。

  垃圾回收器解决了大量的但并非全部的内存分配问题。好比你能够无限期的建立对象和引用它们直到没有内存可使用。垃圾收回收器自身所占用的时间和资源也很是复杂。

  垃圾回收器使用精确的算法来组织内存的分配和再分配,而且对程序员屏蔽细节。内存空间一般从叫做“堆”的内存池中分配。

  垃圾收集的时机取决于垃圾收集器。一般是垃圾占满了整个堆或者一部分的某个临界值的时候开始回收垃圾。

  比较使人满意的内存分配请求包括了在堆中发现一块大小肯定的未被使用的内存,但这倒是一个复杂的任务。主要是问题是大多数动态内存分配算法都要避免碎片化来保持对象的分配和释放都很高效。linux

使人满意的垃圾收集器特质

  一个垃圾回收器必须安全和全面。也就是说,活着的对象不能被错误的释放,而且垃圾对象在经历超过数个较小数字的回收周期之后不能仍然无人认领。
高效的垃圾收集操做也是比较使人满意的,它不会在应用运行期间形成长时间的停顿。在与计算机有关的系统中,须要在时间,空间,和回收频率上寻求平衡。好比,若是堆比较小,收集会很是快可是却容易被用满,所以须要更频繁的回收。相反的,一个比较大的堆填满须要更长的时间,回收频率也会变少,可是回收的时间会变长。

  另一个使人满意指标的垃圾回收器地碎片化的限度。当一个垃圾对象的内存被释放时,释放的空间可能会出现一系列连续的小块,可是这些连续的小块任何一块都不足以分配一个较大的对象。一个消除碎片化的方法叫作压缩,就是下面一系列垃圾回收器所讨论的设计思想。

  可扩展性也很重要。内存的分配不该该成为可扩展性的瓶颈。回收也不该该成为瓶颈。程序员

设计思想

  在设计或者选择垃圾回收算法的时候有几个思想是必须考虑的:web

  • 串行仍是并行

      在串行收集器中,同一个时间只有一件事发生。举个例子,即便是多个CPU的系统,也只有一个CPU用来执行垃圾回收。当并行的垃圾回收器用的时候,垃圾回收任务被同时地分配在不一样的CPU,这些同时地操做使得垃圾回收更快地完成,可是增长了复杂性和潜在碎片的风险。面试

  • 并发 VS Stop-the-world

      当“Stop-the-world”类型收集器执行的时候,整个应用程序会被彻底挂起。相对的,一个或者多个垃圾回收器可以并发地执行,也就是说与应用程序同时执行。一般,一个并发的垃圾收集器本身一般自身是并发的工做,可是偶尔也会形成形成一个短暂的“Stop-the-world”停顿。 “Stop-the-world”要比并发的收集器简单,由于在整个收集期间,堆被冻结而且对象再也不改变。因为某些应用程序的暂停会形成不良的影响,因此这多是一个劣势。相应的,并行的垃圾收集暂停的时间更短,可是垃圾收集器却须要更多当心,由于垃圾收集正在操做的对象可能会同时被应用程序更新。对于并发收集器来讲这些额外的开销会影响性能而且须要更大的堆。算法

  • 压缩 VS 不压缩 VS 拷贝

      当一个垃圾收集器决定内存中的哪些对象存活哪些对象须要回收的时候,它能够压缩内存,把活着的对象移动到一块儿而后完整的恢复剩余的内存。压缩以后,很是容易快速的释放和回收内存。使用一个简单的指针来跟踪下一个能够分配对象的内存位置。与压缩收集器相对应的是,不压缩的收集器原地释放被垃圾对象占用的空间,也就是说,它不会采用与压缩的垃圾回收器移动全部活着的对象的方式来建立内存区域。这样作的优点是垃圾回收会很是快,可是缺点就是会有潜在的碎片。一般来讲,原地释放的内存上从新分配对象也比在压缩的堆上分配内存代价要更昂贵。不压缩的垃圾回收器必须搜索一个连续的足够大的区域来容纳新对象。第三个能够选择的垃圾收集器就是吧或者的对象拷贝或者疏散到不一样的内存区域。这样的好处是源区域能够很快的被清空而且容易的连续分配。缺点是增长了拷贝须要的时间而且可能须要额外的空间。编程

性能指标

  有诸多的指标来计算垃圾收集器的性能,包括:windows

  • 吞吐量:未被垃圾收集的时间占总时间的百分比,应该被认为在一个长时间内。
  • 垃圾回收的天花板:吞吐量的倒数,也就是说,垃圾回收占总时间的百分比。
  • 暂停时间: 当应用程序暂停以执行垃圾回收的时间。
  • 回收频率:多久垃圾回收发生一次,相对与整个应用程序的执行时间。
  • 内存需求:堆大小的测量,好比堆大小
  • 实时性:在对象变成垃圾到内存变得可用的时间。
      一个交互系统可能须要低暂停时间,然而对于一个非交互系统应用程序而言整个可执行的时间却更加剧要。一个实时系统在任什么时候间段下都须要更小的垃圾回收暂停上界和垃圾回收的的占比。在我的电脑中或者嵌入式系统中,对小内存需求可能比较关心。

分代回收

  当一个叫作“分代回收”的垃圾收集被运用的时候,内存被划分红了不一样的代,也就是划分出了不一样的池来持有不一样年龄的对象。好比,最普遍的使用的配置是分红两个代:年轻代和老年代。
  不一样的算法运用在不一样的代来执行垃圾回收。每个算法基于各自代的特性来优化。在许多语言写成的应用程序中,这样的设计是基于弱年代假设(Weak Generational Hypothesis),包括Jav语言:
  1.越早分配的对象越容易失效。

  2.只有少数的老年代对象引用年轻代对象存在。

  年轻代回收相对频繁和快速,由于年轻代的空间一般很小而且包含了大量再也不使用的对象。

  通过几回回收还存活的对象最终会被提高或者终生晋升到老年代。如图1。老年代一般比年轻代大而且增加缓慢。因此,老年代的垃圾回收不频繁,可是会占用更多的时间。

 

  年轻代垃圾回收算法选择一般会在速度上花费高昂的代价,由于年轻代的对象收集会很是频繁,另外一方面,管理老年代垃圾回收的算法则更有效。由于老年代一般占据堆的大部分,而且老年代的算法必须在低回收密度下工做的很好。

4.J2SE5.0中的Hotspot VM的垃圾收集器

  J2SE 5.0 update 6发布的Hotspot VM包含了四中垃圾收集器。全部的垃圾收集器都是分代的。本节描述了分代和回收器的类型,并讨论了为何对象的分配会频繁而且快速和高效,本文也提供了关于每一个垃圾收集器的细节。

HotSpot中的分代

  HotSpot虚拟机被划分红了三个代:年轻代,老年代和一个永久代。大部分对象最初被分配到了年轻代,老年代包含了几回垃圾回收之后还存活的对象。此外一些大的对象一开始就被分配到老年代。永久带持有JVM比较容易找到并管理的对象,好比描述类和方法的对象,也包括类和方法自身。

  年轻代由叫作Eden区和两个较小的survivor区组成。如图2,大部分对象分配在Eden区。(注意,一些大的对象一开始就被分配到老年代)survivor区持有那些至少在一次垃圾收集中存活而且被认为在足够老而且晋升到老年代以前仍然有机会死亡的对象。在任何给定的时间内,一个survivor区老是持有存活的对象,而另一个survivor区则是空置的,直到下一次回收器开始工做。

 

 

垃圾收集的类型

  当年轻代被占满的时候,年轻代的垃圾收集就会执行工做。(有时候也叫做次收集。次收集
垃圾收集按频率可分为: 次收集(Minor Collection):频繁发生在年轻代,收集快速消亡的对象;主收集(Major Collection):年轻代和年老代的全范围收集,频率很低)当老年代被占满或者永久带被占满的时候,会发生整个gc(有时候也叫作主收集),也就是说,全部的代都会被收集。一般,年轻代首先被收集。由于年轻代的算法一般是最高效的。接下来是使用“老年代垃圾收集算法”的垃圾收集器开始在老年代和永久代工做。若是在收集的过程当中发生了压缩,每个代内部都会各自压缩。

  若是年轻代优先被收集,老年代就会由于太满以致于没法接受来自年轻代晋升而来的对象。在这种状况下,除了CMS收集器运行以外,年轻代全部的垃圾回收算法将会被暂停。与之替代的是,老年代的算法将会被运用到整个堆。(CMS的老年代算法是一个特例,由于它没法回收年轻代)。

快速分配

  在下面关于垃圾收集器你将看到,在不少状况下都是从一块连续的很大的内存上面分配对象。利用一个叫作“空闲指针”的技术,使得在内存块上分配对象很高效。指针始终保持最后一个分配的对象的内存位置。当有新的对象须要分配内存时,只须要检查剩余的空间是否存放新的对象,若是可以,更新指针的位置而且初始化对象。

  对于多线程的应用程序来讲,对象的分配须要保证线程安全。若是使用全局锁来保证这些,那么对象的分配就会成为瓶颈且性能降低。所以你,hotspot虚拟机采用了一个叫作“线程分配缓冲(TLAB)”的技术。经过给每一个线程设置本身的缓存来分配对象来提升多线程的内存分配吞吐量。这样每一个TLAB只会有一个线程分配对象,这样不须要锁,空闲指针会移动的很快。但当一个线程将本身的缓存用光之后再分配新的,则必需要同步,可是这并不频繁。HotSpot虚拟机会采起一系列的技术来减小因为使用了线程本地分配缓存而带来内存浪费。好比,TLAB的分配器只会形成Edon区平均大约少于百分之一的损耗。使用Edon和空闲指针可以作到每个分配都高效,这只须要大约10个本地指令。

串行回收器

  在串行收集器中,全部的年轻代和老老年代都是串行的执行的(一次只用一个CPU),串行回收器会产生stop-the-world,也就是说,整个应用在垃圾回收期间会被挂起。

使用串行回收器的年轻代

  图3插图说明了串行回收器年轻代操做的情形。edon区活着的对象被拷贝到了Survivor区,有一种状况是其中一个区太大以致于不能彻底拷贝到Survivor区,这些对象直接拷贝到了老年代。from区存活的相对来讲比较年轻的对象也会被拷贝到另一个Survivor区,相对比较老的对象则直接拷贝到老年代。注意:若是to区已经满了,edon区或者from区将再也不拷贝,无论有多少对象存活。任何在edon区或者from区,通过拷贝过而且还存活的对象,在定义上是不存活。所以他们也不须要被检查。(这些对象就是在下图中标记为“x”对象,尽管事实上垃圾收集器并不会检查或者标记这些对象。)

 

 

  在年轻代的垃圾收集完成之后,Edon区和早先被占用的Survivor区被清空,早先空的Survivor区持有了活着的对象。基于这一点,两个Survivor区交换了角色。如图4

 

 

使用串行收集器的老年代收集器

  在串行收集器中,老年代和永久带经过一种“标记-清除-压缩”的算法。在标记阶段,收集器识别哪些对象是存活的。在清除阶段,清除那些被识别为垃圾的对象。而后收集器则执行“滑动压缩”。把全部活着的对象移动到老年代起始的地方(永久代相似)。这样就在堆得末端留下一个至关大的连续的区块。如图5。压缩容许在老年代或者永久代使用空闲指针的技术分配任何预分配的对象。

 

 

何时使用串行回收器

  串行回收器是大部分运行在客户端类型机器的应用程序的选择,这些应用程序不要求有低延迟。在现在的硬件条件下,串行收集器可以很容易管理一个有64mb的内存,发生一次full gc相对最坏的暂停不会超过半秒的时间。

串行收集器的选择:

  在j2se5中,串行收集器自动被非server模式的机器所选择。如第五节描述的那样,在其余类型的机器上,须要使用-XX:+UseSerialGC来显示地指定。

#并行收集器

  现在,许多java应用程序运行在大内存和多核CPU上面。并行收集器,也就是熟知的高吞吐收集器。它利用了多核CPU的优点,而不是只在一个CPU上执行垃圾收集的工做。

年轻代的并行垃圾收集器

  年轻代的垃圾收集器是串行收集器的并行版本。它仍然是一个具备stop the world 暂停和对象拷贝的收集器。因为使用了多核CPU,在执行的时候确是并发的,这样减小了垃圾收集器的消耗,所以增长了应用程序的吞吐量。图6阐明了串行收集器与并行收集器在年轻代中的不一样。

 

 

老年代使用并行收集器

  老年代的并行收集器工做模式与串行收集器的标记-清除-压缩算法相同。

何时使用并发收集器

  应用程序程序可以从运行在多个CPU的并发收集器受益,而且不会有长时间暂停的限制。可是在某些状况下,好比批处理任务,帐单系统,工资系统,科学计算等系统中,这些应用系统的老年代垃圾收集回收虽然不频繁,可是耗费时间可能会很长。这个时候你就能够考虑使用并行的压缩回收器(下面将要描述的)而不是并发收集器了,由于早先的并发收集器适用于全部的代,并不只仅是年轻代才能使用。

并发收集器的选择

  J2SE 5.0发行版中,并行回收器是server类型的机器的默认的收集器。(文章第五节定义)。在其余类型的机器上,使用XX:+UseParallelGC命令来显示地指定使用并行收集器。

并发压缩收集器

  J2SE 5.0 update 6 引进了并发压缩收集器。不一样于并发收集器的地方在于它为老年代使用了新的算。注意: 并发压缩收集器终会替代并行垃圾回收器

年轻代使用并发压缩收集器

  年轻代的并发压缩收集器与年轻代使用并发收集器的算法同样。

老年代使用并发压缩收集器

  在并发压缩收集器中,老年代和永久带在stop-the-world的过程当中,大部分并发模式都是滑动压缩。收集器分为三步来回收垃圾:第一步,每个代都被逻辑地分为几个固定的区域。在标记阶段,初始的直接可到达的对象被划分到不一样的垃圾收集线程。而后全部活着的对象被并发的标记。当一个对象被肯定是活着的,关于这个区域的信息和这个对象的位置的数据将会被更新。汇总阶段:汇总阶段的操做基于区,而不是基于对象。因为上一次垃圾回收时的压缩操做, 通常来讲代空间的左边区域存活对象的密度会较高. 这种密度高的区域中, 能够回收的空间很少, 因此压缩他们的可用空间的代价过高. 因此汇总阶段作的第一件事情就是测试区域密度。 从最左边的那个区域开始, 一直到找到一个点, 压缩这个点的右边的区域是代价是值得的. 这个点左边的区域叫作密度前缀 (Dense Prefix), 这些区域不会有新的对象写入。 这个点右边的区域将被压缩, 并清除全部死亡对象。汇总阶段计算并存储了每一个压缩区域的存活对象的第一个字节的地址。注意: 汇总阶段目前的是实现是串行执行, 由于相对来讲,标记和压缩阶段的并行执行更重要。

  在压缩阶段,垃圾回收使用从汇总阶段收集而来的数据来肯定哪些区域,而且线程能够独立的拷贝数据到这些区域。这样就造就了在一端是密度很高的对象区块,另一端是一个连续大的可用区块。

何时使用并发压缩算法

  与并行回收算法相似,并行压缩算法对在超过一个CPU容许的应用程序有益。除此以外,老年代的并行操做减小了暂停时间而且并发压缩的收集器更加适合对暂停时间有严格限制的应用。并发压缩算法可能不适用于那些运行在大型共享机的机器上面的应用,他们不容许单个应用程序长时间的垄断CPU。在这些机器上,就得考虑减小垃圾回收的线程(经过–XX:ParallelGCThreads=n)或者选择不一样的回收器。

并行压缩收集器选择

  若是你想指定使用并行压缩收集器,使用-XX:+UseParallelOldGC 命令行选项。

并发的标记-清除收集器

  在不少应用程序中,端到端的吞吐量并与快速响应的时间相比并不重要。年轻代一般状况下并不会形成长时间的停顿。而后老年代虽然并不频繁,可是会形成长时间的停顿,尤为是涉及到很大的堆。为了解决这个问题,HotSpot VM引入了一种叫作并发的标记清除收集器,也叫做低延迟收集器。

年轻代使用并发标记清除收集器

  CMS与年轻代中的并行收集器同样。

老年代使用CMS收集器

  大多数的CMS收集器是在并行地执行。

  CMS收集器开始于一个叫作初始化标记的暂停。初始化标记是标记那些在应用程序中直接可达的活着的对象。而后,在并发标记阶段,收集器标记那些间接可达的活着的对象。因为在标记阶段发生的时候,应用程序不停地在运行而且更新引用,不是全部活着的对象都能在标记阶段被肯定地标记。为了处理这个问题,应用程序将会暂停很短的时间,叫作remark。就是从新标记对在标记阶段中任何发生了改变的对象。由于remark阶段的暂停要比初始化标记的时间要长,因此会使用多线程运行以便提升效率。

  在remark标记的结束之后,全部活着的对象都确保被标记了。因此随后的并发清除阶段就是清除全部的垃圾。图7显示了使用串行的标记清除回收器与并行的标记清除回收器有何不一样。


  因为一些诸如在remark阶段从新检索对象等额外的任务,垃圾回收器增长了一些额外的工做,同时也增长了一些简接消耗。因此对于大多数收集器来讲,在正确性与暂停时间之间都会存在一种平衡和取舍。

  CMS收集器是惟一的不压缩的收集器。也就是说垃圾对象在释放之后,并不会把活着的对象移动到代的一端,参见图8


  这样作虽然节省了时间,但因为自由空间不是连续的,收集器再也不使用一个简单的指针来指示下一个可使用的位置,使得下一个对象能够分配。相反,它如今须要空闲空间的列表。也就是说它如今须要一些列表把未分配的内存区域链接在一块儿,每一次须要分配对象,就必须在适当的列表(基于所需的内存)必须寻找一个区域足够容纳对象,分配到老年代。比用一个简单的bump-the-pointer技术更加昂贵。这也对年轻代的收集产生了额外的开销,由于大多数老年代的对象都是从年轻代晋升而来的。

  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 VM的模式(客户端模式或者服务器模式)都是根据应用程序运行的平台或者操做系统自动选择的。这些自动化选项可以更好的匹配不一样类型的应用程序的须要。固然比先前的发行版本中相比须要的命令行选项更少了。
  值得一提的是,一种新的回收器新的动态适配方法已经加入到并行垃圾收集器中。使用这种方法时,用户能指按期望的行为,而且垃圾回收器能动态的调整堆区域的大小以便试图与用户请求的行为取得一致。这种综合考虑了平台依赖的默认选项和使用用户指望的垃圾收集器的组合模式就叫作人体工程学。整我的体工程学的目标是使用最少的命令行来达到最好的JVM性能。

收集器自动选项的堆大小和虚拟机

一个服务器类型(Server模式)的机器定义以下:

  • 2个或者两个以上的物理处理器
  • 2g或者2g以上的物理内存
      这种服务器类型(Server模式)的定义适用于全部的平台。除了运行在32位Windows操做系统下。

      若是机器不是在服务器类型下的机器,默认的JVM,垃圾收集器和和堆大小以下:

  • 客户端模式的JVM

  • 串行收集器
  • 初始化堆大小4M
  • 堆最大64M
      在server模式的机器,JVM一般是Server模式,除非你想显示的使用client命令行来请求JVM使用client模式。在服务器类型的机器上运行JVM都是server模式的JVM,默认的垃圾收集器是并行收集器。不然是串行收集器。

      在server 类型的机器上运行任何一中JVM模式指定使用并行收集器,默认的初始化参数和最大堆得参数:
  • 初始化堆大小为物理内存的64分之一,上限是1G。注意最小的堆是32M。由于server类型的机器至少有2G内存,64分之一就是32M。
  • 最大的堆是物理内存的4分之一。上限是1G。

  不然,将按照非server类型的机器配置(4M初始堆内存和64M最大内存)默认的值一般会被命令行设置的值覆盖。有关选项在本文第八节说明。

基于行为的并发回收器的优化

  在J2SE 5.0版本中,添加了一种新的优化方法来并行垃圾收集器,它能够按照应用程序的指望的预期行为对垃圾进行收集。使用命令行选项指定所需的行为目标的最大暂停时间和应用程序吞吐量。

最大暂停时间

最大暂停的时间经过以下命令行设置:
-XX:MaxGCPauseMillis=n
这解释为提示并行收集器暂停时间被指望为n毫秒或者更少。并行收集器将调整堆大小和其余垃圾收集相关参数,试图保持垃圾收集停顿时间短于n毫秒。这些调整可能致使垃圾收集器来减小应用程序的总体吞吐量,并在某些状况下所需的暂停时间的目标不可能实现。
最大暂停时间的目标是分别适用于每一代。一般状况下,若是不知足咱们的目标,代会被划分的更小以指望试图达到这个目标。没有默认设置最大暂停时间的目标。

吞吐量目标

  吞吐量目标是以一种测量垃圾收集花费的时间和和垃圾收集花费以外的时间(叫作应用程序花费时间)。这个目标可使用以下命令行指定:
-XX:GCTimeRatio=n
  垃圾回收的时间和应用时间的比率:

  1/(1+ n)

  例如- xx:GCTimeRatio = 19设置了总时间的的5%的目标是垃圾收集的时间。默认的目标是1%(n = 99)。垃圾收集的时间是全部代的总时间。若是吞吐量的目标没有被知足,一代又一代的大小增长,以增长时间集合之间的应用程序能够运行。大的一代须要更多的时间来填满。

性能消耗的目标

  若是吞吐量和最大暂停时间都被知足,垃圾收集器会减小堆得大小直到其中一个目标不被知足(通常会是吞吐量)的临界值。未被达成的目标将会被放到一边。

目标优先级

  并行垃圾收集器首先试图知足最大暂停时间的目标。只有在最大暂停时间知足之后收集器解决吞吐量目标。一样,性能消耗目标是前两个目标已经达到以后才会思考的问题。

6.建议

  在前一节中描述的人体工程学中垃圾收集器,虚拟机以及堆大小的选择,对大部分应用程序是合理的。所以,最初的建议选择和配置一个垃圾收集器是什么都不作!即没有指定使用一个特定的垃圾收集器,等等。让系统根据应用程序正在运行的平台和操做系统自动选择。而后测试应用程序。若是它的性能是能够接受的,有够高的吞吐量和低暂停时间,这样就够了。你不须要排查垃圾收集器或修改选项。

  另外一方面,若是您的应用程序彷佛出现了与垃圾收集有关的性能问题,那么你能作的最简单的事情就是集合应用程序和平台特色,认为默认的垃圾收集器是否合适。若是不是,显式地选择你认为合适的收集器,而后看看是否成为可接受的性能。

你  可使用如第7节中描述的那些工具测量和分析性能。根据结果,您能够考虑修改选项,好比那些控制堆大小和垃圾收集行为。一些最经常使用的具体选项部分8所示。请注意:最好的性能调优方法是测量第一,而后调整。(Measure using tests relevant for how your code will actually be used)。测试用例取决于你的代码如何运行。同时,谨防过分优化,由于应用程序的数据集,硬件,因此甚至垃圾收集器的实现!可能随时间改变。
  本节提供的信息选择一个垃圾收集器和指定堆大小。而后,提供建议,优化并行垃圾收集器,并给出一些建议关于如何处理outofmemoryerror错误。

什么时候选择不一样的垃圾回收器

  在第四节中,介绍个每一个回收器的适用情形。章节五描述了不一样的平台上串行回收器和并行回收器的默认选择。若是你的应用程序或者环境特性与默认的回收器的适用状况不一样,请使用如下的其中一个命令行选项来明确使用一个垃圾回收器:

   –XX:+UseSerialGC 

   –XX:+UseParallelGC

   –XX:+UseParallelOldGC

   –XX:+UseConcMarkSweepGC

堆大小调整

  第五节告诉默认的初始和最大堆大小。这些默认大小可能对大多数状况来讲会工做的很好,可是若是你的应用出现了性能问题(见第7节)或一个OutOfMemoryError(本节稍后讨论)而且已经经过分析肯定是代或者整个堆大小的问题,您能够经过8节中指定的命令行选项修改大小。例如,默认最大堆大小64 mb的non-server-class机器一般是过小了,因此你能够经过- xmx选项指定一个更大的堆。除非你有长时间的暂停问题,尽可能给予尽量多的内存堆。吞吐量可用内存的数量成正比。有足够的可用内存影响垃圾收集的性能是最重要的因素。在已经决定你能够给整个堆的所有内存有多少后,而后您能够考虑调整各个代大小不一样。第二个影响垃圾收集的性能最有影响力的因素是堆的年轻代比例。除非你找到老年代增加过快或这暂停时间过长的问题所在,尽可能给予年轻代更多的多内存。然而,若是你使用的是串行收集器,不要给予年轻代超过总堆大小的一半的内存。

  当你使用一个并发的垃圾收集器,最好指定所指望的行为,而不是准确的堆大小值。让收集器自动和动态修改堆大小以实现这一行为,下面将讲到这一点。

并行收集器的调优策略

  若是(不管是自动或显式地)选择的是并行收集器或并行压缩收集器,而后为您的应用程序指定一个吞吐量目标(见第五节)。不要贸然的更改一个堆的最大值,除非你知道你须要一个大于默认最大堆大小的堆值。堆将增加或缩小规模来支持选择的吞吐量目标。堆得大小在初始化值和变化值之间的振荡是能够预期的。若是堆增加到最大值,在大多数状况下,这意味着在已经最大值的状况下仍是不能达到吞吐量目标。此时为应用程序设置的最大的堆大小值来接近平台上全部的物理内存但不包含引交换分区,再一次运行这个应用程序。若是吞吐量目标仍然没有被实现,那么应用程序的所指望的目标运行时间对于该平台上的可用的内存来讲过高了。若是吞吐量目标能够被实现,可是暂停的时间太长了,会优先选择一个最大的暂停时间。选择一个最大的暂停时间意味着你的吞吐量目标将不会被实现,所以应当选择一个应用程序能够妥协的值。堆大小会在垃圾回收器试图知足相互竞争的目标之间进行摇摆,即便应用程序达到一个稳定的状态。这之间的压力来自达到吞吐量目标(这将会须要一个更大的堆)与最大暂停时间和最小内存需求(这将会须要一个更小的堆)。

如何处理内存溢出异常

  许多开发人员必须解决的一个常见的问题就是应用程序由于java.lang.OutOfMemoryError而终止。这个错误在没有足够的空间来分配一个对象时抛出。垃圾收集,不能分配任何进一步可用的的可用空间以适应一个新对象并且堆又不能进一步扩大。OutOfMemoryError错误并不必定意味着内存泄漏。这个问题多是配置问题,例如若是指定的堆大小(若是未指定或默认大小)对应用程序来讲是不够的。

  OutOfMemoryError错误诊断的第一步是检查错误消息。在抛出异常后,“java.lang.OutOfMemoryError”会提供进一步的信息。这里有一些常见的例子,额外的信息多是什么,它可能意味着什么,以及如何应对:

Java堆空间

  这代表了一个对象没法在堆上分配。这个问题可能只是一个配置问题。你能够捕获到这个错误,例如,若是使用-Xmx命令行选项来指明最大的堆大小(或者是默认值)没法知足应用程序的需求。他也可能代表一个再也不被使用的对象不被垃圾回收器回收,由于应用程序无心地保持了这些对象的引用。HAT工具(见章节七)能够用来观察全部的可达对象和明确哪个引用来保持哪个对象的存活。另外一个潜在的错误来源有多是在应用程序中过多地使用了 finalizers 以至于线程调用 finalizers 没法跟得上添加finalizers到队列的速度。Jconsole管理工具能够用来监控在销毁期间的对象的数目。

永久带空间

  这代表永久代已经满了。如前所述,堆得这部分区域是JVM的堆存储用来存储元数据的。若是一个应用程序加载大量的类,永久代就可能须要增长。能够经过指定的命令行选项-xx:MaxPermSize = n,其中n指定大小。

数组大小超过了VM的限制

  这意味着应用程序试图分配比堆大小的数组。例如,若是一个应用程序试图分配512 mb的数组但最大堆大小为256。 mb,那么就会抛出这个错误。在大多数状况下,问题极可能是堆大小是过小或应用程序中,数组大小被计算错误,使得数组大小很大。

  第7节中描述的一些工具能够用来诊断OutOfMemoryError问题。一些最有用的工具,这个任务是堆分析工具(HAT),jconsole管理工具,jmap工具组织选项。

分析回收器性能的工具

  有一系列的监控和诊断能够利用起来,来计算垃圾回收器的性能。本段提供了这些工具的简要说明。要得到更多的信息,请访问第九部分关于工具和故障排除的章节。

–XX:+PrintGCDetails 命令行

  一个最简单获取垃圾收集器初始化信息就是使用–XX:+PrintGCDetails命令。对于任何收集器来讲,输出的结果包括了垃圾回收以前和以后各个代存活的对象的大小,每个代能够用的空间以及垃圾回收所花费的时间。

–XX:+PrintGCTimeStamps 命令行

  输出垃圾回收器开始的时间戳。若是使用了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中。它是一个动态连接库接口JVM使用Java虚拟机(JVM TI)工具界面。它写出概要信息到一个文件或一个套接字ASCII和二进制格式。这些信息能够进一步由前端工具分析器处理。
  HPROF可以提供CPU使用率,堆分配统计和监控争用配置文件。此外,它能够输出完整的堆转储和报告的全部Java虚拟机监视器和线程。HPROF是有用的在分析性能、锁争用内存泄漏等问题。参见9 HPROF连接文档。

HAT: Heap Analysis Tool

  堆分析工具(HAT)用来帮助调试无心地对象保留。这个术语用来描述一个再也不被须要的对象因为被一个存活的对象所引用而保持存活。HAT提供了一个方便的手段来浏览对象在堆中的快照。这个工具容许必定数量的查询,包含“向我提供全部从根集合到对象的引用路径”,参见章节九的HAT文档连接。

8.与垃圾收集有关的关键选项

  许多命令行选项能够用来选择一个垃圾收集器,指定堆或代大小,修改垃圾收集行为,得到垃圾收集统计数据。本节显示了一些最经常使用的选项。更完整的列表和详细信息可用的各类选项,参见9。注意:你指定的数字能够以“m”或“m”mb,为千字节“k”或“k”,和“g”或“g”g。

垃圾收集器选项

选项 垃圾收集器选择
–XX:+UseSerialGC 串行
–XX:+UseParallelGC 并行
–XX:+UseParallelOldGC 并行压缩
–XX:+UseConcMarkSweepGC 并行标记清除(CMS)

垃圾回收器的统计分析选项

选项 描述
–XX:+PrintGC 输出每次垃圾收集的基础信息
–XX:+PrintGCDetails 输出每次垃圾回收的更多额外的信息
–XX:+PrintGCTimeStamps 输出每次垃圾回收事件开始的时间戳。使用–XX:+PrintGC 或者 –XX:+PrintGCDetails 来输出更多信息

堆和代大小

选项 默认 描述
–Xmsn 第五节 初始堆化大小,byte计数
–Xmxn 参见第五节 最大的堆大小,byte技术
–XX:MinHeapFreeRatio=minimum and –XX:MaxHeapFreeRatio=maximum 40最小,70最大 空闲空间占总空间比例的目标。这会运用于任何一代上。例如,若是最小值是30,而且空闲空间占该代上的空间比例小于30%,那么这个代空间就会扩展直到知足30%的空闲空间。近似的,若是最大值是60而且自由空间的比例已经超过60%,代空间的大小就会收缩直到自由空间只占到60%。
–XX:NewSize=n 平台依赖 默认年轻代的大小,byte计算
–XX:NewRatio=n Client JVM 为2,8为Server JVM 年轻代和老年代之间的比例。例如,若是n是3,那么Eden区的比例是1:3,合并后的大小和幸存者空间总大小的占年轻代和老年代四分之一。
–XX:SurvivorRatio=n 32 survivor区与Edon区的比例。例如,若是n是7,每一个幸存者空间是年轻一代的九分之一(八分之一,由于有两个survivor空间)。
–XX:MaxPermSize=n 平台相关 永久带最大空间

并发或者并发压缩收集器的选项

选项 默认值 描述
–XX:ParallelGCThreads=n CPU的数量 垃圾收集器线程的数量
–XX:MaxGCPauseMillis=n 没有默认值 代表指望暂停时间少于n毫秒
–XX:GCTimeRatio=n 99 垃圾收集花在总时间的比例(1/(n+1))

CMS收集器选项

选项 默认值 描述
–XX:+CMSIncrementalMode 禁止 支持并发模式阶段逐步完成,并发阶段按期中止回收以便应用程序继续运行
–XX:+CMSIncrementalPacing 没有默认值 代表指望暂停时间少于n毫秒
–XX:ParallelGCThreads=n CPU的数量 年轻代的垃圾收集器和线程数和老年代并发收集器并发部分的线程数。

9.更多信息

Hotspot垃圾回收和性能调优

  • Java HotSpot中的垃圾收集

((http://www.devx.com/Java/Article/21977))

人体工程学

Server类型的侦探

(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 VM 选项

(http://java.sun.com/docs/hotspot/VMOptions.html)

  • Solaris 和 Linux 选项

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html)

工具和问题诊断

Java2平台,5.0版本-问题定位和诊断指南
(http://java.sun.com/j2se/1.5/pdf/jdk50_ts_guide.pdf )

HPROF: A Heap/CPU Profiling Tool in J2SE 5.0
(http://java.sun.com/developer/technicalArticles/Programming/HPROF.html)
HAT:堆分析工具
(https://hat.dev.java.net/)

析构

如何处理JAVA析构中的内存留用问题:

(http://www.devx.com/Java/Article/30192)

杂项

• J2SE 5.0 发行版注解

(http://java.sun.com/j2se/1.5.0/relnotes.html)

• JavaTM 虚拟机

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/index.html)

• Sun JavaTM 实时 System (Java RTS)

(http://java.sun.com/j2se/realtime/index.jsp)

• 关于垃圾收集的通用书籍:


Garbage Collection: Algorithms for Automatic Dynamic Memory Management by Richard Jones and Rafael Lins, John Wiley & Sons, 1996.

额外阅读-垃圾回收算法:

  任何一种垃圾收集算法通常要作2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。
HotSpot使用的垃圾回收算法为分代回收算法(Generational Collector)
   大多数垃圾回收算法使用了根集(rootset)这个概念(有了这个概念应该就能解决面试中被问到的互为引用的孤独岛的状况);所谓根集就是正在执行的java程序能够访问的引用变量的集合(包括局部变量、参数、类变量),程序可使用引用变量访问对象的属性和调用对象的方法。垃圾收集首选须要肯定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能做为垃圾被回收,这也包括从根集间接可达的对象。而根集经过任意路径不可达的对象符合垃圾收集的条件,应该被回收。下面介绍几个经常使用的算法。

一、引用计数法(referencecountingcollector)

引用计数法是惟一没有使用根集的垃圾回收得法,该算法使用引用计数器来区分存活对象和再也不使用的对象。通常来讲,堆中的每一个对象对应一个引用计数器。当每一次建立一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1。当对象出了做用域后(该对象丢弃再也不使用),引用计数器减1,一旦引用计数器为0,对象就知足了垃圾收集的条件。

基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须实时运行的程序。但引用计数器增长了程序执行的开销,由于每次对象赋给新的变量,计数器加1,而每次现有对象出了做用域生,计数器减1。

二、tracing算法(tracingcollector)

   tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每一个可达对象设置一个或多个位。在扫描识别过程当中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.

三、compacting算法(compactingcollector)

   为了解决堆碎片问题,基于tracing的垃圾回收吸取了compacting算法的思想,在清除的过程当中,算法将全部的对象移到堆的一端,堆的另外一端就变成了一个相邻的空闲内存区,收集器会对它移动的全部对象的全部引用进行更新,使得这些引用在新的位置能识别原来的对象。在基于compacting算法的收集器的实现中,通常增长句柄和句柄表。

四、coping算法(copingcollector)

   该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分红一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于coping算法的垃圾收集就从根集中扫描活动对象,并将每一个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

   一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分红对象面和空闲区域面,在对象面与空闲区域面的切换过程当中,程序暂停执行。

五、generation算法(generationalcollector) 分代回收算法(Generational Collector)

stop-and-copy垃圾收集器的一个缺陷是收集器必须复制全部的活动对象,这增长了程序等待时间,这是coping算法低效的缘由。在程序设计中有这样的规律:多数对象存在的时间比较短,少数的存在时间比较长。所以,generation算法将堆分红两个或多个,每一个子堆做为对象的一代(generation)。因为多数对象存在的时间比较短,随着程序丢弃不使用的对象,垃圾收集器将从最年轻的子堆中收集这些对象。在分代式的垃圾收集器运行后,上次运行存活下来的对象移到下一最高代的子堆中,因为老一代的子堆不会常常被回收,于是节省了时间。

六、adaptive算法(adaptivecollector)

在特定的状况下,一些垃圾收集算法会优于其它算法。基于adaptive算法的垃圾收集器就是监控当前堆的使用状况,并将选择适当算法的垃圾收集器。

java 8 中的垃圾收集

java8从Hotspot JVM中删除了永久代,因此咱们再也不须要为永久代设置大小,也就是不用设置PermSize和MaxPermSize。
在java8以前方法区是做为堆的永久代来实现的,启动JVM时咱们须要设置永久代的大小,垃圾回收器也要回收这部分区域,并且会抛出内存溢出异常。借鉴于JRockit虚拟机,java8以后 Hotspot 虚拟机从堆中完全删除了永久代。
—把方法区中的String和静态变量移到了堆中。
—把其余的东西(好比类结构)放到了本地内存中,JVM会直接负责这部分的内存回收。

总之,咱们再也不须要设置PermSize和MaxPermSize;方法区的内存溢出将再也不出现,除非本地内存耗光。

本文引用自:https://juejin.im/post/58fca9465c497d00580068ff

相关文章
相关标签/搜索