jvm垃圾回收 http://blog.csdn.net/zhyhang/article/details/17233251
jvm垃圾回收简史 http://www.ibm.com/developerworks/cn/java/j-jtp10283/
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%的时间用于垃圾回收。
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 |