jvm垃圾回收 http://blog.csdn.net/zhyhang/article/details/17233251 java
jvm垃圾回收简史 http://www.ibm.com/developerworks/cn/java/j-jtp10283/ c++
jvm垃圾收集器总结 http://my.oschina.net/shiinnny/blog/388748?fromerr=kgbOEhyF 程序员
GC(Garbage Collection),是JAVA/.NET中的垃圾收集器。算法
Java是由C++发展来的,它摈弃了C++中一些繁琐容易出错的东西,引入了计数器的概念,其中有一条就是这个GC机制(C#借鉴了JAVA)编程
编程人员容易出现问题的地方,忘记或者错误的内存回收会致使程序或系统的不稳定甚至崩溃,Java提供的GC功能能够自动监测对象是否超过做用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操做方法。因此,Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。缓存
对于程序员来讲,分配对象使用new关键字;释放对象时,只要将对象全部引用赋值为null,让程序不可以再访问到这个对象,咱们称该对象为"不可达的".GC将负责回收全部"不可达"对象的内存空间,大部语言的内存管理方式,都是放弃对对象地址的引用,下一次使用时直接覆盖。服务器
对于GC来讲,当程序员建立对象时,GC就开始监控这个对象的地址、大小以及使用状况。一般,GC采用有向图的方式记录和管理堆(heap)中的全部对象。经过这种方式肯定哪些对象是"可达的",哪些对象是"不可达的".当GC肯定一些对象为"不可达"时,GC就有责任回收这些内存空间。可是,为了保证 GC可以在不一样平台实现的问题,Java规范对GC的不少行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、何时进行回收等重要问题都没有明确的规定。所以,不一样的JVM的实现者每每有不一样的实现算法。这也给Java程序员的开发带来行多不肯定性。本文研究了几个与GC工做相关的问题,努力减小这种不肯定性给Java程序带来的负面影响。多线程
最大垃圾回收暂停:指定垃圾回收时的最长暂停时间(暂停时间影响到内存的从新分配时间,因此越短效率越高),经过-XX:MaxGCPauseMillis=<N>指定。<N>为毫秒.若是指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减小应用的吞吐量。并发
吞吐量:吞吐量为垃圾回收时间与非垃圾回收时间的比值,经过-XX:GCTimeRatio=<N>来设定,公式为1/(1+N)。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认状况为99,即1%的时间用于垃圾回收。jvm
1. c/c++是手动分配/回收内存,jvm是自动管理内存的分配/回收。这也是一大坨java工程师的应用能在一块儿相安无事的缘由之一。
2. 在垃圾回收时,会出现整个应用停顿(由于这个时候必须中止内存的分配),从而引出吞吐量。
3. 当应用愈来愈大的时候,标记法花费的时间愈来愈长,反而影响了运行效率。这也是如今并发回收器大量运用的缘由(回收器线程和应用线程同时运行)。
1.暂停时间:收集器是否中止全部工做来进行垃圾收集?要中止多长时间?暂停是否有时间限制?
2.暂停的可预测性:垃圾收集暂停是否规划为在用户程序方便而不是垃圾收集器方便的时间发生?
3.CPU 占用:总的可用 CPU 时间用在垃圾收集上的百分比是多少?
4.内存大小:许多垃圾收集算法须要将堆分割成独立的内存空间,其中一些空间在某些时刻对用户程序是不可访问的。这意味着堆的实际大小可能比用户程序的最大堆驻留空间要大几倍。
5.虚拟内存交互:在具备有限物理内存的系统上,一个完整的垃圾收集在垃圾收集过程当中可能会错误地将很是驻页面放到内存中来进行检查。由于页面错误的成本很高,因此垃圾收集器正确管理引用的区域性 (locality) 是很必要的。
6.缓存交互:即便在整个堆能够放到主内存中的系统上 ―― 实际上几乎全部 Java 应用程序均可以作到这一点,垃圾收集也经常会有将用户程序使用的数据冲出缓存的效果,从而影响用户程序的性能。
7.对程序区域性的影响:虽然一些人认为垃圾收集器的工做只是收回不可到达的内存,可是其余人认为垃圾收集器还应该尽可能改进用户程序的引用区域性。整理收集器和复制收集器在收集过程当中从新安排对象,这有可能改进区域性。
8.编译器和运行时影响:一些垃圾收集算法要求编译器或者运行时环境的重要配合,引用计数。如当进行指针分配时更新引用计数。这增长了编译器的工做,由于它必须生成这些簿记指令,同时增长了运行时环境的开销,由于它必须执行这些额外的指令。这些要求对性能有什么影响呢?它是否会干扰编译时优化呢?
垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存,从这点看,二者是矛盾的。所以,在现有的垃圾回收方式中,要进行垃圾回收前,通常都须要暂停整个应用(即:暂停内存的分配),而后进行垃圾回收,回收完成后再继续应用。这种实现方式是最直接,并且最有效的解决两者矛盾的方式。
内存分配的越大,暂停的时间越长:当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大。一些对相应时间要求很高的应用,好比最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就颇有可能超过这个限制,在这种状况下,垃圾回收将会成为系统运行的一个瓶颈。
解决:并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,可是由于须要在新生成对象的同时又要回收对象,算法复杂性会大大增长,系统的处理能力也会相应下降,同时,“碎片”问题将会比较难解决。
分代的垃圾回收策略,是基于这样一个事实:不一样的对象的生命周期是不同的。所以,不一样生命周期的对象能够采起不一样的收集方式,以便提升回收效率。
在Java程序运行的过程当中,会产生大量的对象,其中有些对象是与业务信息相关,好比Http请求中的Session对象、线程、Socket链接,这类对象跟业务直接挂钩,所以生命周期比较长。可是还有一些对象,主要是程序运行过程当中生成的临时变量,这些对象生命周期会比较短,好比:String对象,因为其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次便可回收。
试想,在不进行对象存活时间区分的状况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,由于每次回收都须要遍历全部存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,由于可能进行了不少次遍历,可是他们依旧存在。所以,分代垃圾回收采用分治的思想,进行代的划分,把不一样生命周期的对象放在不一样代上,不一样代上采用最适合它的垃圾回收方式进行回收。
如上图,虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)、持久代(Permanent Generation)
其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。
(1)年轻代:
全部新生成的对象首先都是放在年轻代的。年轻代的目标就是尽量快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(通常而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的而且此时还存活的对象,将被复制“年老区(Tenured)”。须要注意,Survivor的两个区是对称的,没前后关系,因此同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。并且,Survivor区总有一个是空的。同时,根据程序须要,Survivor区是能够配置为多个的(多于两个),这样能够增长对象在年轻代中的存在时间,减小被放到年老代的可能。
(2)年老代:
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
(3)持久代:
用于存放静态文件,现在Java类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。持久代大小经过-XX:MaxPermSize=<N>进行设置。
4.3.1 Scavenge GC
通常状况下,当新对象生成,而且在Eden申请空间失败时(剩余的连续的内存空间不能建立这个对象),就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到Survivor区。而后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。由于大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,因此Eden区的GC会频繁进行。于是,通常在这里须要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
4.3.2 .Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC由于须要对整个对进行回收,因此比Scavenge GC要慢,所以应该尽量减小Full GC的次数。在对JVM调优的过程当中,很大一部分工做就是对于FullGC的调节。有以下缘由可能致使Full GC:
· 年老代(Tenured)被写满《运行时的主要Full GC方式》
· 持久代(Perm)被写满
· System.gc()被显示调用
·上一次GC以后Heap的各域分配策略动态变化
比较古老的回收算法。原理是此对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,引用收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。
缺点:若是两个对象相会引用,则没法达到回收效果。最典型的就是java中的观察这模式,须要对象之间的相互引用。
从程序运行的根节点出发,遍历整个对象引用,查找存活的对象
垃圾回收从哪儿开始的呢?即,从哪儿开始查找哪些对象是正在被当前系统使用的。上面分析的堆和栈的区别,其中栈是真正进行程序执行地方,因此要获取哪些对象正在被使用,则须要从Java栈开始,每个线程都会在java stack 区划分一个线程栈,而后实现第一个栈帧。同时,一个栈是与一个线程对应的,所以,若是有多个线程的话,则必须对这些线程对应的全部的栈进行检查。
同时,除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。这样,以栈或寄存器中的引用为起点,咱们能够找到堆中的对象,又从这些对象找到对堆中其余对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就造成了一颗以Java栈中引用所对应的对象为根节点的一颗对象树,若是栈中有多个引用,则最终会造成多颗对象树。在这些对象树上的对象,都是当前系统运行所须要的对象,不能被垃圾回收。而其余剩余对象,则能够视为没法被引用到的对象,能够被当作垃圾进行回收。
所以,垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器...)。而最简单的Java栈就是Java程序执行的main函数。
它中止全部工做,收集器从根开始访问每个活跃的节点,标记它所访问的每个节点。走过全部引用后,收集就完成了,而后就对堆进行清除(即对堆中的每个对象进行检查),全部没有标记的对象都做为垃圾回收并返回空闲列表。
优势:
1.消耗低
2.解决了,引用计数的相互引用问题
缺点:
1.收集暂停很长
2.会产生内存碎片。当一个对象须要new的时候,若是在eden区没有连续的实用的内存,就会又发生gc。这又会使整个jvm效率变得低下。
堆被分红两个大小相等的半空间,其中一个包含活跃的数据,另外一个未使用。当活跃的空间占满之后,程序就会中止,活跃的对象被从活跃的空间复制到不活跃的空间中。空间的角色就会转换,原来不活跃的空间成为了新的活跃空间。直接把活跃的数据内存复制到未使用区,而后放弃活跃区,下次又直接覆盖。
复制收集的优势是只访问活跃的对象,这意味着不会检查垃圾对象,也不须要将它们页交换到内存中或者送到缓存中。复制收集器的收集周期时间是由活跃对象的数量决定的。不过,复制收集器由于要将数据从一个空间复制到另外一个空间、调整全部引用以指向新备份而增长了成本。特别是,长寿的对象在每次收集时都要来回复制。
优势:
1. 不会检查垃圾对象
2. 复制成本低
3. 没有内存碎片
缺点:
1.消耗内存:须要另一个内存空间,做为不活跃区。
此算法结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,
1.从根节点开始标记全部被引用对象,
2.遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
6.2 首次回收(survivor里面没有数据)
(1)增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么缘由JDK5.0中的收集器没有使用这种算法的。
(2)分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不一样生命周期的对象使用不一样的算法(上述方式中的一个)进行回收。如今的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
7.2.1 串行收集:
(1)串行收集使用单线程处理全部垃圾回收工做,由于无需多线程交互,实现容易,并且效率比较高。
(2)其局限性也比较明显,即没法使用多处理器的优点,因此此收集适合单处理器机器。固然,此收集器也能够用在小数据量(100M左右)状况下的多处理器机器上。
(3)使用-XX:+UseSerialGC打开
(4)场景:适用状况:数据量比较小(100M左右);单处理器下而且对响应时间无要求的应用。缺点:只能用于小型应用
7.2.2 并行收集
(1)并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。
(2)当回收时,全部的cpu都去处理回收线程。会形成停顿时间,用户体验很差。
(3)使用-XX:+UseParallelGC.打开
(4)使用-XX:ParallelGCThreads=<N>设置并行垃圾回收的线程数。此值能够设置与机器处理器数量相等。
(5)场景:适用状况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。缺点:垃圾收集过程当中应用响应时间可能加长
7.2.3并发收集
(1)相对于串行收集和并行收集而言,前面两个在进行垃圾回收工做时,须要暂停整个运行环境(有垃圾回收程序在运行)。应用线程和回收线程同时运行,停顿时间短,吞吐量高。如今的服务器基本都是用的该类。
(2)使用-XX:+UseConcMarkSweepGC 打开
(3)设置-XX:CMSInitiatingOccupancyFraction=<N>指定还有多少剩余堆时开始执行并发收集
(4)场景:适用状况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境
类别 | serial collector (单线程收集器) |
parallel collector ( 并行收集器) |
concurrent collector (并发收集器) |
介绍 | 使用单线程去完成全部的gc工做,没有线程间的通讯,这种方式会相对高效 | 使用多线程的方式,利用多CUP来提升GC的效率,主要以到达必定的吞吐量为目标 | 使用多线程的方式,利用多CUP来提升GC的效率,并发完成大部分工做,使得gc pause短 |
适用场景 | 单处理器机器且没有pause time的要求 | 适用于科学技术和后台处理 有中规模/大规模数据集大小的应用且运行在多处理器上,关注吞吐量(throughput) |
适合中大规模数据集的应用,应用服务器,电信领域,关注response time,而不是throughput |
使用参数 | Client模式下默认:可以使用 强制使用参数:-XX:+UseSerialGC 优势:对server应用没什么优势 缺点:慢,不能充分发挥硬件资源 |
Server模式下默认 --YGC:SP --FGC:Parallel MSC 强制使用参数: -XX:+UseParallelGC或-XX:+UseParallelOldGC --ParallelGC表明FGC为Parallel MSC --ParallelOldGC表明FGC为Parallel Compacting |
可用-XX:+UseConcMarkSweepGC强制指定 优势:对old进行回收时,对应用形成的暂停时间很是短,适合对latency要求比较高的应用 缺点: 1.内存碎片和浮动垃圾 2.old去的内存分配效率低 3.回收的整个耗时比较长 4.和应用争抢CPU |
内存回收触发条件 | YGC:eden空间不足 FGC:old空间不足 perm空间不足 显示调用System.gc(),包括RMI等的定时触发 |
YGC:eden空间不足 FGC:old空间不足 perm空间不足 显示调用System.gc(),包括RMI等的定时触发 |
CMS GC: 1.old Gen使用率大的比率,默认为92% 2.配置了CMSClassUnloadingEnabled,且Perm Gen的使用达到必定的比率默认为92% 3.Hotspot本身根据估计决定是否要触法 4.在配置了ExplictGCInvokesConcurrent的状况下显示调用了System.gc() |
内存回收触发工做 | YGC 1.清空eden+from中全部no-ref的对象占用的内存 2.将eden+from中的全部存活的对象copy到to中 3.在这个过程当中一些对象将晋升到old中: --to放不下的 --存活次数超过tenuring threshold的 从新计算Tenuring Threshold; 单线程作以上动做,GC全程暂停应用 FGC 1.若是配置了CollectGen0First,则先触发YGC 2.清空heap中no ref的对象,permgen中已经被卸载的classloader中加载的class的信息 3.单线程作以上动做 4.全程暂停应用 |
YGC 同serial动做基本相同,不一样点: 1.多线程处理 2.YGC的最后不只从新计算Tenuring Threshold,还会从新调整Eden和From的大小 FGC 1.如配置了ScavengeBeforeFullGC(默认),则先触发YGC(??) 2.MSC:清空heap中的no ref对象,permgen中已经被卸载的classloader中加载的class信息,并进行压缩 3.Compacting:清空heap中部分no ref的对象,permgen中已经被卸载的classloader中加载的class信息,并进行部分压缩 多线程作以上动做. |
YGC 同serial动做基本相同,不一样点: 1.多线程处理 CMSGC: 1.old gen到达比率时只清除old gen中no ref的对象所占用的空间 2.perm gen到达比率时只清除已被清除的classloader加载的class信息 FGC 同serial |
细节参数 | -XX:+UseSerialGC强制使用 -XX:SurvivorRatio=x,控制eden/s0/s1的大小 |
-XX:SurvivorRatio=x,:控制eden/s0/s1的大小 -XX:UseAdaptiveSizePolicy :去掉YGC后动态调整eden from已经tenuringthreshold的动做 -XX:ParallelGCThreads=4:设置并行的线程数 |
-XX:CMSInitiatingOccupancyFraction :设置old gen使用到达多少比率时触发 -XX:CMSInitiatingPermOccupancyFraction:设置Perm Gen使用到达多少比率时触发 -XX:+UseCMSInitiatingOccupancyOnly:禁止hostspot自行触发CMS GC |