java 深入理解JVM--JVM垃圾回收机制

来源:互联网 发布:驾校坡道数据 编辑:程序博客网 时间:2024/06/06 05:28

为什么要分代分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。如何分代如图所示:最上面如何分代 虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。年轻代:所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。年老代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。持久代:用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。什么情况下触发垃圾回收由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。Scavenge GC一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。Full GCJVM基础概念总结:数据类型、堆与栈JVM概念之Java对象的大小与引用类型漫谈Java理念的转变与JVM的五大优势漫谈JVM的基本垃圾回收算法JVM垃圾回收面临的问题对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:· 年老代(Tenured)被写满· 持久代(Perm)被写满· System.gc()被显示调用·上一次GC之后Heap的各域分配策略动态变化

总结:

使用垃圾收集器要注意的地方下面将提出一些有关垃圾收集器要注意的地方,垃圾收集器知识很多,下面只列出一部分必要的知识:(1)每个对象只能调用finalize( )方法一次。如果在finalize( )方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。(2)垃圾收集器跟踪每一个对象,收集那些不可触及的对象(即该对象不再被程序引用 了),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用该对象的finalize( )方法(如果有)。如果在finalize()方法中,又使得该对象被程序引用(俗称复活了),则该对象就变成了可触及的对象,暂时不会被垃圾收集了。但是由于每个对象只能调用一次finalize( )方法,所以每个对象也只可能 "复活 "一次。(3)Java语言允许程序员为任何方法添加finalize( )方法,该方法会在垃圾收集器交换回收对象之前被调用。但不要过分依赖该方法对系统资源进行回收和再利用,因为该方法调用后的执行结果是不可预知的。(4)垃圾收集器不可以被强制执行,但程序员可以通过调研System.gc方法来建议执行垃圾收集。记住,只是建议。一般不建议自己写System.gc,因为会加大垃圾收集工作量。

新生代向老生代转变的几个条件:新生代超过N次GC都没被回收,将变为老生代。N可以通过JVM参数指定,-XX:MaxTenuringThreshold=N. Parallel收集器默认是15.如果实例需要的内存大小超过M,则直接分配到老生代。 M可以通过JVM参数指定。-XX:PretenureSizeThreshold=M。如果suvivor中 同一代的实例在占据了survivor区(稍后讲)的一半以上,则年龄大于等于此代的一同进入老生代。Minor GC时,Survivor无法容纳存活新生代,由老生代空间担保,新生代被挪入老生代。

(1)分代收集的好处:当代商业虚拟机都采用分代收集的算法。一般将Java堆分为新生代和老年代。这样就可以根据各个年代的特点采用最适当的收集算法了。在新生代中,每次垃圾收集都会发现有大批的对象死去,只有少数存活,那就采用复制算法。而老年代中因为对象存活率高,没有额外的空间进行分配担保,就必须采用“标记-整理”的算法来进行回收。(2)复制算法的初衷和改进复制算法的原理是将可用的内存分成大小相等的两块,每次只使用其中的一块儿。当这一块儿内存用完之后,就将还活着的对象复制到另外一块儿上面,然后再把已经使用过的内存空间一次性的清理掉。研究表明:新生代的对象98%都是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存划分为一块较大的eden和两块较小的survivor空间。每次使用eden和其中的一块survivor。当回收的时候,将eden和survivor中还存活的对象一次性的拷贝到另外的一块儿survivor空间,最后清理掉eden和刚才使用过的survivor空间。

JVM的垃圾回收过程首先从GC Roots开始进行可达性分析,判断哪些是不可达对象。对于不可达对象,判断是否需要执行其finalize方法,如果对象没有覆盖finalize方法或已经执行过finalize方法则视为不需要执行,进行回收;如果需要,则把对象加入F-Queue队列。对于F-Queue队列里的对象,稍后虚拟机会自动建立一个低优先级的线程去触发其finalize方法,但不会等待这个方法返回。如果在finalize方法的执行过程中,对象重新被引用,那么进行第二次标记时将被移出F-Queue,在finalize方法执行完成后,对象仍然没有被引用,则进行回收。对于被移出F-Queue的对象,如果它下一次面临回收时,将不会再执行其finalize方法。

垃圾回收器Serial : 新生代收集器,使用copy算法。特点是单线程,当GC运行时,stop the world,所有的用户线程都必须暂停,等GC完成。缺点是停顿时间太长。ParNew: 新生代收集器,使用copy,特点是多线程,也是停止用户线程,GC以后再运行用户线程。Parallel: 新生代收集器,使用copy,但它注重JVM的吞吐量(JVM运行用户线程时间/GC线程时间),在提高吞吐量上有非常好的性能。它提供了很多可控参数,帮助调节JVM的吞吐量和停顿时间。Serial Old: 老生代收集器,使用标记整理算法,特点跟serial 一样。Parallel Old:老生代收集器,使用标记整理,产生于JDK 1.6,和Parallel搭配。在之前,Parallel只能和Serial old搭配,总体性能不是特别理想,现在双P。CMS(Concurrent Mark Sweep):老生代收集器,使用标记清除。它的特点是可以和用户线程并发,可以减小停顿时间,但牺牲了吞吐量。CMS不能和Parallel搭配,一半和ParNew搭配。CMS的并发特性导致它3大缺点,1)对CPU要求高,2)无法清除因并发产生的浮动垃圾,3)容易产生碎片,为了清理碎片,每几次CMS之后,可以使用参数控制是否进行空间压缩。由于CMS是并发的,CMS需要预留空间给用户线程,一半老生代空间达到68%,就启动CMS. CMS过程中一旦程序失败,则发生CMS error。然后将使用Serial old作为它的备选,重新收集。CMS三个参数:XX:CMSInitiatingOccupancyFaction  设置老年代空间使用多少后出发CMS垃圾收集。一般是68%XX:UseCMSCompactAtFullCollection 每次CMS Full GC以后,做一次碎片整理。XX:CMSFullGCsBeforeCompaction 规定在多少次CMS Full GC以后,做一次碎片整理。G1,传说中的新收集器,可以指定停顿时间,更高的效率。Serial 和 serial old适合Client端的JVM。Server端常用的是Parallel + Parallel Old, ParNew + CMS.Parallel + Parallel Old 的吞吐量高ParNew + CMS可减小停顿时间。使用哪一个收集器,可以在JVM启动时用VM参数来指定。具体参考官方JVM options。

堆内存分配策略与GC触发堆的大小由下面参数决定。-Xms 堆最小值, -Xmx 堆最大值。堆的大小动态调整-XX:MaxHeapFreeRatio=70 当堆剩余空间达到70%,堆将变为Xms最小值。-XX:MinHeapFreeRatio=40 当堆剩余空间低于到40%,堆将变成Xmx最大值。堆分新生代和老生代。-Xmn 为新生代大小。也可以用-XX:NewRatio=2来分配New/Old的比例。 2为默认值。新生代又划分Eden和2*survivor。-XX:survivorRatio=8来规定Eden/survivor的比例。8为默认值。实例首先尝试分配在新生代eden上。如果实例所占内存超出限制XX:PretenureSizeThreshold,则直接分配到老生代。新生代分配内存时,如果eden空间不足,则触发新生代的GC, minor GC。老生代分配内存时,如果空间不足,则触发老生代的GC, full GC。老生代的GC比新生代GC要慢很多倍。老生代GC频率也比新生代低。新生代minor GC时,如果存活的空间survivor容纳不下,则需要用老生代空间担保。如果老生代也容纳不下,就需要老生代做一次Full GC。 JVM会计算以往minor GC时新生代晋升老生代的平均大小,如果平均大小大于老生代剩余空间,则有很大可能发生担保失败,所以在minor GC前,先触发一次Full GC. 如果小于,则查看是否允许担保失败,如果允许,就直接进行minor GC。不允许,则先Full GC,再minor GC。-XX:+HandlePromotionFailure 是允许老生代担保失败的发生。每次minor GC前,都会检查老生代剩余空间。JVM会计算以往minor GC时新生代晋升老生代的平均大小。此参数用于当平均大小小于老生代剩余空间时。以往小于不代表这次也小于,风险还是存在。如果允许担保失败,则直接进行minor GC, minor GC失败后,再Full GC. 如果不允许担保失败,则必须先运行Full GC,再minor GC。这样子新生代GC时,如果survivor无法容纳存活实例,则将部分复制到老生代担保中。

JVM回收算法1、标记-清除2、复制3、标记-清除-压缩JVM垃圾对象标记1、引用计数2、引用跟踪垃圾收集器现在java虚拟机的收集器都是把对象按其存活时间分为年轻代、年老代、永久代。然后采用上述回收算法回收,主要收集器有:1、串行收集器2、并行收集器3、并发收集器JVM年代划分Young (年轻代) 年轻代分三个区。一个 Eden 区,两个 Survivor 区。大部分对象在 Eden 区                 中生成。当 Eden 区满时,还存活的对象将被复制到 Survivor 区(两个中的 一个),当这个 Survivor 区满时,此区的存活对象将被复制到另外一个  Survivor 区,当这个 Survivor 去也满了的时候,从第一个 Survivor 区复 制过来的并且此时还存活的对象,将被复制 " 年老区 (Tenured)" 。需要注意 Survivor 的两个区是对称的,没先后关系,所以同一个区中可能同时存在从  Eden 复制过来 对象,和从前一个 Survivor 复制过来的对象,而复制到年老区 的只有从第一个 Survivor 去过来的对象。而且, Survivor 区总有一个是空的。Tenured(年老代)年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。Perm (持久代)  用于存放静态文件,如今 Java 类、方法等。持久代对垃圾回收没有显著影响,                 但是有些应用可能动态生成或者调用一些 class ,例如 Hibernate 等,在这种 时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代 大小通过 -XX:MaxPermSize= 进行设置。GC 类型GC 有两种类型: Scavenge GC 和 Full GC 。Scavenge GC    一般情况下,当新对象生成,并且在 Eden 申请空间失败时,就好触发 Scavenge                GC ,堆 Eden 区域进行 GC ,清除非存活对象,并且把尚且存活的对象移动到 Survivor 区。然后整理 Survivor 的两个区。只作用年轻代Full GC        对整个堆进行整理,包括 Young 、 Tenured 和 Perm 。 Full GC 比 Scavenge GC               要慢,因此应该尽可能减少 Full GC 。有如下原因可能导致 Full GC :Tenured 被写满               Perm 域被写满,System.gc() 被显示调用,上一次 GC 之后 Heap 的各域分配策略       动态变化上述知识点详解JVM回收算法1、标记-清除   此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶               段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。2、复制        此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历               当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使       用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过       出现 " 碎片 " 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。3、标记-清除-压缩  此算法结合了 " 标记 - 清除 " 和 " 复制 " 两个算法的优点。也是分两阶段,第               一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且       把存活对象 " 压缩 " 到堆的其中一块,按顺序排放。此算法避免了 " 标记 - 清除 "        的碎片问题,同时也避免了 " 复制 " 算法的空间问题。JVM垃圾对象标记1、引用计数    无法处理循环引用的问题2、引用跟踪    此方法用来替代引用计数,解决引用计数存在的问题垃圾收集器                JVM 给了三种选择:串行收集器、并行收集器、并发收集器 ,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下, JDK5.0 以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。 JDK5.0 以后, JVM 会根据当前系统配置进行判断。 1、串行收集器  使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无               法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小       数据量( 100M 左右)情况下的多处理器机器上。可以使用 -XX:+UseSerialGC 打开。2、并行收集器                  对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用 -XX:+UseParallelGC. 打开。并行收集器在 J2SE5.0 第六 6 更新上引入,在 Java SE6.0中进行了增强 -- 可以堆年老代进行并行收集。如果年老代不使用并发收集的话,是使用单线程进行垃圾回收,因此会制约扩展能力。使用 -XX:+UseParallelOldGC 打开。                使用 -XX:ParallelGCThreads= 设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等。                此收集器可以进行如下配置:                最大垃圾回收暂停 : 指定垃圾回收时的最长暂停时间,通过 -XX:MaxGCPauseMillis= 指定。 为毫秒 . 如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减少应用的吞吐量。                吞吐量 : 吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过 -XX:GCTimeRatio= 来设定,公式为 1/ ( 1+N )。例如, -XX:GCTimeRatio=19 时,表示 5% 的时间用于垃圾回收。默认情况为 99 ,即 1% 的时间用于垃圾回收。并发收集器      可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。                这种回收期不进行内存碎片整理,使用 -XX:+UseConcMarkSweepGC 打开。配合-XX:+UseCMSCompactAtFullCollection                -XX:CMSFullGCsBeforeCompaction这两个指令使用三种收集器的选择串行处理器:-- 适用情况:数据量比较小( 100M 左右);单处理器下并且对响应时间无要求的应用。-- 缺点:只能用于小型应用并行处理器:-- 适用情况: " 对吞吐量有高要求 " ,多 CPU 、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。-- 缺点:应用响应时间可能较长并发处理器:-- 适用情况: " 对响应时间有高要求 " ,多 CPU 、对应用响应时间有较高要求的中、大型应用。举例: Web 服务器 / 应用服务器、电信交换、集成开发环境。典型配置堆设置    -Xms: 初始堆大小    -Xmx: 最大堆大小    -XX:NewSize=n: 设置年轻代大小    -XX:NewRatio=n: 设置年轻代和年老代的比值。如 : 为 3 ,表示年轻代与年老代比值为 1 : 3 ,年轻代占整个年轻代年老代和的 1/4    -XX:SurvivorRatio=n: 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如: 3 ,表示 Eden : Survivor=3 : 2 ,一个 Survivor 区占整个年轻代的 1/5    -XX:MaxPermSize=n: 设置持久代大小收集器设置    -XX:+UseSerialGC: 设置串行收集器    -XX:+UseParallelGC: 设置并行收集器,这个设置只能指定年轻代的收集器,年老代继续使用默认收集器    -XX:+UseParalledlOldGC: 设置并行年老代收集器    -XX:+UseConcMarkSweepGC: 设置并发收集器,这种回收器只对年老代起作用,年轻代使用默认值,也可通过-XX:+UseParNewGC指定年轻代为并行收集器垃圾回收统计信息    -XX:+PrintGC    -XX:+PrintGCDetails    -XX:+PrintGCTimeStamps    -Xloggc:filename并行收集器设置    -XX:ParallelGCThreads=n: 设置并行收集器收集时使用的 CPU 数。并行收集线程数。    -XX:MaxGCPauseMillis=n: 设置并行收集最大暂停时间    -XX:GCTimeRatio=n: 设置垃圾回收时间占程序运行时间的百分比。公式为 1/(1+n)并发收集器设置    -XX:+CMSIncrementalMode: 设置为增量模式。适用于单 CPU 情况。    -XX:ParallelGCThreads=n: 设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并行收集线程数。

Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的allocated,然后不停的~析构。于是,有人就提出,能不能写一段程序在实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?

1960 基于MITLisp首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时Java还没有出世呢!所以实际上GC并不是Java的专利,GC的历史远远大于Java的历史!

那究竟GC为我们做了什么操作呢?

 

1、    哪些内存需要回收?

2、    什么时候回收?

3、    如何回收?

 

这时候有人就会疑惑了,既然GC已经为我们解决了这个矛盾,我们还需要学习GC么?当然当然是肯定的,那究竟什么时候我们还需要用到的呢?

 

1、    排查内存溢出

2、    排查内存泄漏

3、    性能调优,排查并发瓶颈

 

我们知道,GC主要处理的是对象的回收操作,那么什么时候会触发一个对象的回收的呢?

1、    对象没有引用

2、    作用域发生未捕获异常

3、    程序在作用域正常执行完毕

4、    程序执行了System.exit()

5、    程序发生意外终止(被杀进程等)

其实,我们最容易想到的就是当对象没有引用的时候会将这个对象标记为可回收对象,那么现在就有一个问题,是不是这个对象被赋值为null以后就一定被标记为可回收对象了呢?我们来看一个例子:

 

package com.yhj.jvm.gc.objEscape.finalizeEscape;

 

import com.yhj.jvm.gc.objEscape.pojo.FinalizedEscapeTestCase;

 

 

/**

 * @Described:逃逸分析测试

 * @author YHJ create at 2011-12-24 下午05:08:09

 * @FileNmae com.yhj.jvm.gc.finalizeEscape.FinalizedEscape.java

 */

public class FinalizedEscape {

    public static void main(String[] args) throwsInterruptedException {

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape=null;

       System.gc();

       Thread.sleep(100);

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

    }

}

package com.yhj.jvm.gc.objEscape.pojo;

/**

 * @Described:逃逸分析测试用例

 * @author YHJ create at 2011-12-24 下午05:07:05

 * @FileNmae com.yhj.jvm.gc.pojo.TestCaseForEscape.java

 */

public class FinalizedEscapeTestCase {

 

    public static FinalizedEscapeTestCase caseForEscape = null;

    @Override

    protected void finalize() throws Throwable {

       super.finalize();

       System.out.println("哈哈,我已逃逸!");

       caseForEscape = this;

    }

}

 

程序的运行结果回事什么样子的呢?

我们来看这段代码

 

1、  System.out.println(FinalizedEscapeTestCase.caseForEscape);

2、  FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

3、  System.out.println(FinalizedEscapeTestCase.caseForEscape);

4、  FinalizedEscapeTestCase.caseForEscape=null;

5、  System.gc();

6、  Thread.sleep(100);

7、    System.out.println(FinalizedEscapeTestCase.caseForEscape);

 

1、    当程序执行第一行是,因为这个对象没有值,结果肯定是null

2、    程序第二行给该对象赋值为新开辟的一个对象

3、    第三行打印的时候,肯定是第二行对象的hash代码

4、    第四行将该对象重新置为null

5、    第五行触发GC

6、    为了保证GC能够顺利执行完毕,第六行等待100毫秒

7、    第七行打印对应的值,回事null么?一定会是null么?

我们来看一下对应的运行结果



 本例中打印了

GC的日志,让我们看的更清晰一点,我们很清晰的看出,最后一句打印的不是null,并且子啊之前,还出现了逃逸的字样。说明这个对象逃逸了,在垃圾回收之前逃逸了,我们再来看这个pojo的写法,就会发现,我们重写了方法finalize,而这个方法就相当于C++中的析构方法,在GC回收之前,会先调用一次这个方法,而这个方法又将this指针指向他自己,因此得以成功逃逸!可见,并不是这个对象被赋值为null之后就一定被标记为可回收,有可能会发生逃逸!

 

下面我们来看一下几种垃圾收集算法

1、              JDK1.2之前,使用的是引用计数器算法,即当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!这种算法在JDK1.2之前的版本被广泛使用,但是随着业务的发展,很快出现了一个问题

当我们的代码出现下面的情形时,该算法将无法适应

a)         ObjA.obj = ObjB

b)         ObjB.obj - ObjA

                 这样的代码会产生如下引用情形 objA指向objB,而objB又指向objA,这样当其他所有的引用都消失了之后,objAobjB还有一个相互的引用,也就是说两个对象的引用计数器各为1,而实际上这两个对象都已经没有额外的引用,已经是垃圾了。

               



 

 

2、              根搜索算法

                   根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

 



 

 

目前java中可作为GC Root的对象有

1、    虚拟机栈中引用的对象(本地变量表)

2、    方法区中静态属性引用的对象

3、    方法区中常量引用的对象

4、    本地方法栈中引用的对象(Native对象)

说了这么多,其实我们可以看到,所有的垃圾回收机制都是和引用相关的,那我们来具体的来看一下引用的分类,到底有哪些类型的引用?每种引用都是做什么的呢?

Java中存在四种引用,每种引用如下:

1、  强引用

只要引用存在,垃圾回收器永远不会回收

Object obj = new Object();

//可直接通过obj取得对应的对象 obj.equels(new Object());

而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

2、  软引用

非必须引用,内存溢出之前进行回收,可以通过以下代码实现

Object obj = new Object();

SoftReference<Object> sf = new SoftReference<Object>(obj);

obj = null;

sf.get();//有时候会返回null

这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

3、  弱引用

第二次垃圾回收时回收,可以通过如下代码实现

Object obj = new Object();

WeakReference<Object> wf = new WeakReference<Object>(obj);

obj = null;

wf.get();//有时候会返回null

wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null

弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器

4、  虚引用(幽灵/幻影引用)

           垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

Object obj = new Object();

PhantomReference<Object> pf = new PhantomReference<Object>(obj);

obj=null;

pf.get();//永远返回null

pf.isEnQueued();//返回从内存中已经删除

虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。

虚引用主要用于检测对象是否已经从内存中删除。

在上文中已经提到了,我们的对象在内存中会被划分为5块区域,而每块数据的回收比例是不同的,根据IBM的统计,数据如下图所示:

 



 

 我们知道,方法区主要存放类与类之间关系的数据,而这部分数据被加载到内存之后,基本上是不会发生变更的,

Java堆中的数据基本上是朝生夕死的,我们用完之后要马上回收的,而Java栈和本地方法栈中的数据,因为有后进先出的原则,当我取下面的数据之前,必须要把栈顶的元素出栈,因此回收率可认为是100%;而程序计数器我们前面也已经提到,主要用户记录线程执行的行号等一些信息,这块区域也是被认为是唯一一块不会内存溢出的区域。在SunHostSpot的虚拟机中,对于程序计数器是不回收的,而方法区的数据因为回收率非常小,而成本又比较高,一般认为是“性价比”非常差的,所以Sun自己的虚拟机HotSpot中是不回收的!但是在现在高性能分布式J2EE的系统中,我们大量用到了反射、动态代理、CGLIBJSPOSGI等,这些类频繁的调用自定义类加载器,都需要动态的加载和卸载了,以保证永久带不会溢出,他们通过自定义的类加载器进行了各项操作,因此在实际的应用开发中,类也是被经常加载和卸载的,方法区也是会被回收的!但是方法区的回收条件非常苛刻,只有同时满足以下三个条件才会被回收!

 

1、所有实例被回收

2、加载该类的ClassLoader被回收

3Class对象无法通过任何途径访问(包括反射)

好了,我们现在切入正题,Java1.2之前主要通过引用计数器来标记是否需要垃圾回收,而1.2之后都使用根搜索算法来收集垃圾,而收集后的垃圾是通过什么算法来回收的呢?

1、    标记-清除算法

2、    复制算法

3、    标记-整理算法

我们来逐一过一下

1、    标记-清除算法

        



 

 

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。

标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片!

2、    复制算法

 



 
 
 
 

 复制算法采用从根集合扫描,并将存活对象复制到一块新的,没有使用过的空间中,这种算法当控件存活的对象比较少时,极为高效,但是带来的成本是需要一块内存交换空间用于进行对象的移动。也就是我们前面提到的

s0 s1等空间。

 

3、    标记-整理算法

 



 
 
 

 标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

 

分代回收算法我们知道,JVM为了优化内存的回收,进行了分代回收的方式,对于新生代内存的回收(minor GC)主要采用复制算法,下图展示了minor GC的执行过程。

 



 
 

 对于新生代和旧生代,

JVM可使用很多种垃圾回收器进行垃圾回收,下图展示了不同生代不通垃圾回收器,其中两个回收器之间有连线表示这两个回收器可以同时使用。

 

 



 

 而这些垃圾回收器又分为串行回收方式、并行回收方式合并发回收方式执行,分别运用于不同的场景。如下图所示

 

 



 

 下面我们来逐一介绍一下每个垃圾回收器。

 

1、    Serial收集器

看名字我们都可以看的出来,这个属于串行收集器。其运行示意图如下



 
 

 Serial

收集器是历史最悠久的一个回收器,JDK1.3之前广泛使用这个收集器,目前也是ClientVM ServerVM 44GB以下机器的默认垃圾回收器。串行收集器并不是只能使用一个CPU进行收集,而是当JVM需要进行垃圾回收的时候,需要中断所有的用户线程,知道它回收结束为止,因此又号称“Stop The World 的垃圾回收器。注意,JVM中文名称为java虚拟机,因此它就像一台虚拟的电脑一样在工作,而其中的每一个线程就被认为是JVM的一个处理器,因此大家看到图中的CPU0CPU1实际为用户的线程,而不是真正机器的CPU,大家不要误解哦。

 

串行回收方式适合低端机器,是Client模式下的默认收集器,对CPU和内存的消耗不高,适合用户交互比较少,后台任务较多的系统。

Serial收集器默认新旧生代的回收器搭配为Serial+ SerialOld

2、    ParNew收集器

ParNew收集器其实就是多线程版本的Serial收集器,其运行示意图如下

 



 
 
 

 同样有

Stop The World的问题,他是多CPU模式下的首选回收器(该回收器在单CPU的环境下回收效率远远低于Serial收集器,所以一定要注意场景哦),也是Server模式下的默认收集器。

 

3、    ParallelScavenge

ParallelScavenge又被称为是吞吐量优先的收集器,器运行示意图如下

 



 
 

 ParallelScavenge

所提到的吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%。在当今网络告诉发达的今天,良好的响应速度是提升用户体验的一个重要指标,多核并行云计算的发展要求程序尽可能的使用CPU和内存资源,尽快的计算出最终结果,因此在交互不多的云端,比较适合使用该回收器。

 

4、    ParallelOld

ParallelOld是老生代并行收集器的一种,使用标记整理算法、是老生代吞吐量优先的一个收集器。这个收集器是JDK1.6之后刚引入的一款收集器,我们看之前那个图之间的关联关系可以看到,早期没有ParallelOld之前,吞吐量优先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量优先的性能,自从JDK1.6之后,才能真正做到较高效率的吞吐量优先。其运行示意图如下

 



 
  

 5、

    SerialOld

 

SerialOld是旧生代Client模式下的默认收集器,单线程执行;在JDK1.6之前也是ParallelScvenge回收新生代模式下旧生代的默认收集器,同时也是并发收集器CMS回收失败后的备用收集器。其运行示意图如下

 



 

 6、

    CMS

 

CMS又称响应时间优先(最短回收停顿)的回收器,使用并发模式回收垃圾,使用标记-清除算法,CMSCPU是非常敏感的,它的回收线程数=CPU+3/4,因此当CPU2核的实惠,回收线程将占用的CPU资源的50%,而当CPU核心数为4时仅占用25%。他的运行示意图如下

 



 

 CMS

模式主要分为4个过程

 

 



  

 在初始标记的时候,需要中断所有用户线程,在并发标记阶段,用户线程和标记线程

并发执行,而在这个过程中,随着内存引用关系的变化,可能会发生原来标记的对象被释放,进而引发新的垃圾,因此可能会产生一系列的浮动垃圾,不能被回收。

 

CMS 为了确保能够扫描到所有的对象,避免在Initial Marking 中还有未标识到的对象,采用的方法为找到标记了的对象,并将这些对象放入Stack 中,扫描时寻找此对象依赖的对象,如果依赖的对象的地址在其之前,则将此对象进行标记,并同时放入Stack 中,如依赖的对象地址在其之后,则仅标记该对象。

在进行Concurrent Marking minor GC 也可能会同时进行,这个时候很容易造成旧生代对象引用关系改变,CMS 为了应对这样的并发现象,提供了一个Mod Union Table 来进行记录,在这个Mod Union Table中记录每次minor GC 后修改了的Card 的信息。这也是ParallelScavenge不能和CMS一起使用的原因。

CMS产生浮动垃圾的情况请见如下示意图

 



 

 

在运行回收过后,c就变成了浮动垃圾。

由于CMS会产生浮动垃圾,当回收过后,浮动垃圾如果产生过多,同时因为使用标记-清除算法会产生碎片,可能会导致回收过后的连续空间仍然不能容纳新生代移动过来或者新创建的大资源,因此会导致CMS回收失败,进而触发另外一次FULL GC,而这时候则采用SerialOld进行二次回收。

同时CMS因为可能产生浮动垃圾,而CMS在执行回收的同时新生代也有可能在进行回收操作,为了保证旧生代能够存放新生代转移过来的数据,CMS在旧生代内存到达全部容量的68%就触发了CMS的回收!

7、    GarbageFirst(G1 )

我们再来看垃圾回收器的总图,刚才我们可以看到,我在图上标记了一个?,其实这是一个新的垃圾回收器,既可以回收新生代也可以回收旧生代,SunHotSpot 1.6u14以上EarlyAccess版本加入了这个回收器,sun公司预期SunHotSpot1.7发布正式版,他是商用高性能垃圾回收器,通过重新划分内存区域,整合优化CMS,同时注重吞吐量和响应时间,但是杯具的是被oracle收购之后这个收集器属于商用收费收集器,因此目前基本上没有人使用,我们在这里也就不多介绍,更多信息可以参考oracle新版本JDK说明。


G1将整个Java堆划分为大小相等的独立区域(Region);新生代和老年代不再是物理隔离的,都由一组不连续的Region组成。

G1的特点:

  • 并行与并发:充分利用多CPU缩短Stop-The-World停顿时间,在收集过程中用并发的方式让Java线程继续执行。
  • 分代收集:仍然有分代的概念,不需要其他收集器配合,独立管理整个GC堆。
  • 空间整合:从整体看,是基于“标记-整理”算法实现的,从局部(两个Region之间)看是基于“复制”算法的。在运行期间不会产生内存碎片。
  • 可预测的停顿:G1跟踪各个Region里垃圾堆积值的价值大小,维护一个优先级队列,每次根据允许的时间,优先回收价值最大的Region。(这也是Garbage First的由来)

Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。

G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序对引用类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查引用的对象是否处于不同的Region中(在其他收集器中就是检查是否老年代中的对象引用了新生代中对象),如果是,通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

G1垃圾回收主要有4个阶段:

  • 初始标记:只标记GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。此阶段需要暂停用户线程。
  • 并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象;耗时较长,可与用户线程并发执行。
  • 最终标记:修正在并发标记期间有变动的标记记录。
  • 刷选回收:对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间制定回收计划,进行垃圾回收。

下面我们再来看下JVM的一些内存分配与回收策略

1、    优先在Edon上分配对象

 

代码示例

package com.yhj.jvm.gc.edenFirst;

/**

 * @DescribedEdon优先划分对象测试

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old

 *   8  1   1  10

 * @author YHJ create at 2012-1-下午04:44:43

 * @FileNmae com.yhj.jvm.gc.edenFirst.EdonFirst.java

 */

public class EdonFirst {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[2*ONE_MB];

       testCase2 = new byte[2*ONE_MB];

       testCase3 = new byte[2*ONE_MB];

//     testCase1 = null;

//     testCase2 = null;

//     testCase3 = null;

       testCase4 = new byte[2*ONE_MB];

    }

 

}

运行结果



 

 结果分析

 

从运行结果我们可以很清晰的看到,eden8MB的存储控件(通过参数配置),前6MB的数据优先分配到eden区域,当下一个2MB存放时,因空间已满,触发一次GC,但是这部分数据因为没有回收(引用还在,当赋值为null后则不会转移),数据会被复制到s0区域,但是s0区域不够存储,因此直接放入老生代区域,新的2MB数据存放在eden区域

2、    大对象直接进入老生代

 

代码示例

package com.yhj.jvm.gc.bigObjIntoOld;

/**

 * @Described:大对象直接进入老生代测试

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old

 *   8  1   1  10

 * @author YHJ create at 2012-1-下午05:28:47

 * @FileNmae com.yhj.jvm.gc.bigObjIntoOld.BigObjIntoOld.java

 */

public class BigObjIntoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[8*ONE_MB];

//     testCase2 = new byte[2*ONE_MB];

//     testCase3 = new byte[2*ONE_MB];

//     testCase1 = null;

//     testCase2 = null;

//     testCase3 = null;

//     testCase4 = new byte[2*ONE_MB];

    }

 

}

运行结果



 

 结果分析

 

我们看到,没有触发GC日志,而数据是直接进入老生代的

3、    年长者(长期存活对象)进入老生代

 

代码示例:

package com.yhj.jvm.gc.longLifeTimeIntoOld;

/**

 * @Described:当年龄大于一定值的时候进入老生代  默认值15

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:MaxTenuringThreshold=1-XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old  age

 *   8  1   1  10   1

 * @author YHJ create at 2012-1-下午05:39:16

 * @FileNmaecom.yhj.jvm.gc.longLifeTimeIntoOld.LongLifeTimeIntoOld.java

 */

public class LongLifeTimeIntoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[1*ONE_MB/4];

       testCase2 = new byte[7*ONE_MB+3*ONE_MB/4];

       testCase2 = null;

       testCase3 = new byte[7*ONE_MB+3*ONE_MB/4];

       testCase3 = null;

       testCase4 = new byte[ONE_MB];

    }

 

}

 

运行结果



 

 结果分析

 

从代码中我们可以看到,当testCase1划分为0.25MB数据,进行多次大对象创建之后,testCase1应该在GC执行之后被复制到s0区域(s0足以容纳testCase1),但是我们设置了对象的年龄为1,即超过1岁便进入老生代,因此GC执行2次后testCase1直接被复制到了老生代,而默认进入老生代的年龄为15。我们通过profilter的监控工具可以很清楚的看到对象的年龄,如图所示

 



 

 右侧的年代数目就是对象的年龄

 

4、    群体效应(大批中年对象进入老生代)

 

代码示例

package com.yhj.jvm.gc.dynamicMoreAVG_intoOld;

/**

 * @Describeds0占用空间到达50%直接进入老生代

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:MaxTenuringThreshold=15-XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old  age

 *   8  1   1  10  15

 *   0.5 0  0   7.5

 *   7.5 0.5 0  7.5

 *   7.5  0  0 8

 * @author YHJ create at 2012-1-下午05:50:40

 * @FileNmae com.yhj.jvm.gc.dynamicMoreAVG_intoOld.MoreAVG_intoOld.java

 */

public class MoreAVG_intoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[7*ONE_MB+ONE_MB/2];

       testCase2 = new byte[ONE_MB/2];

       testCase3 = new byte[7*ONE_MB+ONE_MB/2];

       testCase3 = null;

       testCase4 = new byte[7*ONE_MB+ONE_MB/2];

      

//     testCase1 = new byte[7*ONE_MB+3*ONE_MB/4];

//     testCase2 = new byte[ONE_MB/4];

//     testCase3 = new byte[7*ONE_MB+3*ONE_MB/4];

      

    }

 

}

运行结果



 

 结果分析

 

我们看到,当创建后testCase3testCase2被移动到s0区域,当被释放后,继续创建testCase3,按理说testCase2应该移动到s1区域,但是因为超过了s1区域的1/2,因此直接进入老生代

5、    担保GC(担保minorGC)

担保GC就是担保minorGC能够满足当前的存储空间,而无需触发老生代的回收,由于大部分对象都是朝生夕死的,因此,在实际开发中这种很起效,但是也有可能会发生担保失败的情况,当担保失败的时候会触发FullGC,但是失败毕竟是少数,因此这种一般是很划算的。

 

代码示例

package com.yhj.jvm.gc.securedTransactions;

/**

 * @Described:担保交易测试

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc-XX:-HandlePromotionFailure  无担保

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc-XX:+HandlePromotionFailure  有担保

 * Edon s0 s1 old  

 *   8  1   1  10  

 * @author YHJ create at 2012-1-下午06:11:17

 * @FileNmaecom.yhj.jvm.gc.securedTransactions.SecuredTransactions.java

 */

public class SecuredTransactions {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4,testCase5,testCase6,testCase7;

       testCase1 = new byte[2*ONE_MB];

       testCase2 = new byte[2*ONE_MB];

       testCase3 = new byte[2*ONE_MB];

       testCase1 = null;

       testCase4 = new byte[2*ONE_MB];

       testCase5 = new byte[2*ONE_MB];

       testCase6 = new byte[2*ONE_MB];

       testCase4 = null;

       testCase5 = null;

       testCase6 = null;

       testCase7 = new byte[2*ONE_MB];

      

    }

 

}

 运行结果

1、  无担保


 2、



   有担保

 



 

 结果分析

 

 

我们可以很清楚的看到,当无担保的时候,触发了一次FullGC 而有担保的情况下,只有monorGC则完成了回收,大大提升了效率。

当我们注释掉对应的代码

 

//     testCase4 = null;

//     testCase5 = null;

//     testCase6 = null;

 

的时候,就会引发担保失败,如下图所示

 



 JVM

默认情况是是开启担保的,无需设置参数。


垃圾收集器参数总结

-XX:+<option> 启用选项
-XX:-<option> 不启用选项
-XX:<option>=<number> 
-XX:<option>=<string>

参数描述

-XX:+UseSerialGC

Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收-XX:+UseParNewGC打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收-XX:+UseConcMarkSweepGC使用ParNew + CMS +  Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。-XX:+UseParallelGCJvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge +  Serial Old的收集器组合进行回收
-XX:+UseParallelOldGC使用Parallel Scavenge +  Parallel Old的收集器组合进行回收
-XX:SurvivorRatio新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1-XX:PretenureSizeThreshold直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配-XX:MaxTenuringThreshold晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代-XX:UseAdaptiveSizePolicy动态调整java堆中各个区域的大小以及进入老年代的年龄-XX:+HandlePromotionFailure是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留-XX:ParallelGCThreads设置并行GC进行内存回收的线程数-XX:GCTimeRatioGC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效-XX:MaxGCPauseMillis设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效-XX:CMSInitiatingOccupancyFraction设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70-XX:+UseCMSCompactAtFullCollection
由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效-XX:+CMSFullGCBeforeCompaction
设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用-XX:+UseFastAccessorMethods
原始类型优化-XX:+DisableExplicitGC
是否关闭手动System.gc-XX:+CMSParallelRemarkEnabled
降低标记停顿-XX:LargePageSizeInBytes
内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m

Client、Server模式默认GC

 新生代GC方式老年代和持久GC方式

Client

Serial 串行GCSerial Old 串行GCServerParallel Scavenge  并行回收GCParallel Old 并行GC

Sun/oracle JDK GC组合方式

 新生代GC方式老年代和持久GC方式

-XX:+UseSerialGC

Serial 串行GCSerial Old 串行GC-XX:+UseParallelGCParallel Scavenge  并行回收GCSerial Old  并行GC-XX:+UseConcMarkSweepGCParNew 并行GCCMS 并发GC 
当出现“Concurrent Mode Failure”时
采用Serial Old 串行GC
-XX:+UseParNewGCParNew 并行GCSerial Old 串行GC-XX:+UseParallelOldGCParallel Scavenge  并行回收GCParallel Old 并行GC-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
Serial 串行GCCMS 并发GC 
当出现“Concurrent Mode Failure”时
采用Serial Old 串行GC



转载自:http://jbutton.iteye.com/blog/1569746

https://github.com/chenruiao/ares/blob/master/java/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.txt

http://blog.csdn.net/java2000_wl/article/details/8030172

参考:

Java垃圾回收机制:http://www.jianshu.com/p/778dd3848196

jvm垃圾收集器与内存分配策略:http://my.oschina.net/indestiny/blog/214276

垃圾收集器与Java编程:http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak2/

JVM垃圾回收总结:http://blog.csdn.net/dc_726/article/details/7934101

JVM垃圾回收(GC)原理:http://chenchendefeng.iteye.com/blog/455883

内存泄漏及JVM垃圾回收:http://ldbjakyo.iteye.com/blog/1490242

Java垃圾收集器:http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html

Java垃圾回收机制:http://blog.csdn.net/zsuguangh/article/details/6429592

HotSpot 垃圾回收算法实现:http://coderbee.net/index.php/jvm/20131105/557

Java/JVM垃圾回收机制和算法总结:http://banaba.iteye.com/blog/2007526

0 0