JVM内存回收机制
标签: JVM GC 垃圾回收 内存管理html
当JVM建立对象遇到内存不足的时候,JVM会自动触发垃圾回收garbage collecting(简称GC)操做,将再也不使用但仍存在JVM内存中的对象当作垃圾同样直接清理掉,释放被占用的内存空间,供新建立的对象使用。
那么问题来了,要让系统可以自动实现不被引用对象的回收,有几个问题须要解决:java
关于第二点,对于oracle Hotspot VM的 GC操做主要回收的内存区域是JVM中的堆(分为年轻代和年老代,年轻代又分Eden和两个survivor区域),JVM内存结构不是本文的重点,咱们在看本文前要对JVM内存结构有必定的了解,这里不做详细分析。算法
下面的算法回答了who的问题。数据库
每一个对象建立的时候,会分配一个引用计数器,当这个对象被引用的时候计数器就加1,当不被引用或者引用失效的时候计数器就会减1。任什么时候候,对象的引用计数器值为0就说明这个对象不被使用了,就认为是“垃圾”,能够被GC处理掉。数组
评价:缓存
以一些特定的对象做为基础原始对象,或者称做“根”,不断往下搜索,到达某一个对象的路径称为引用链。
若是一个对象和根对象之间有引用链,即根对象到这个对象是可到达的,则这个对象是活着的,不是垃圾,还不能回收。例如,假设有根对象O,O引用了A对象,同时A对象引用了B对象,B对象又引用了C对象,那么对象C和根对象O之间的路径的可达的,C对象就不能当作垃圾对象。引用链为O->A->B->C。
反之,若是一个对象和根对象之间没有引用链,根对象到这个对象的路径是不可达的,那么这个对象就是可回收的垃圾对象。多线程
评价:并发
根搜索算法是如今GC使用的搜索算法。oracle
能够当作GC roots的对象有如下几种:jvm
虚拟机栈中的引用的对象。(java栈的栈帧本地变量表)
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。(声明为final的常量对象)
本地方法栈中JNI的引用的对象。(本地方法栈的栈帧本地变量表)
下面是从网上找来的图,将就看看:GC ROOTS就是跟对象节点,蓝色的是可达的引用链,引用链上的对象是活着的,不能被当作垃圾对象回收。相反暗灰色的路径表示不可达的路径,这些对象将会被回收。每一个圈圈里面的数字,表示其被引用的次数,没错,就是上面说到的引用计数法的计数值。
这里讨论的是oracle的Hotspot VM常见的垃圾回收算法。使用的搜索算法都是基于根搜索算法实现的。
该算法分两步执行:
1) 标记Mark:从GC ROOTS开始,遍历堆内存区域的全部根对象,对在引用链上的对象都进行标记。这样下来,若是是存活的对象就会被作了标记,反之若是是垃圾对象,则没作有标记。GC很容易根据有没有被作标记就完成了垃圾对象回收。
2) 清除Sweep:遍历堆中的全部的对象(标记阶段遍历的是全部根节点),找到未被标记的对象,直接回收所占的内存,释放空间。
评价:
标记-清除算法操做的对象是【垃圾对象】,对于活着的对象(被标记的对象),它则直接不理睬。
复制算法把内存区间一分为二,有对象存在的一半区间称为“活动区间”,没有对象存在处于空闲状态的空间则为“空闲区间”。
当内存空间不足时触发GC,先采用根搜索算法标记对象,而后把活着的对象所有复制到另外一半空闲区间上,复制算法的“复制”就来自这一操做。复制到另外一半区间的时候,严格按照内存地址依次排列要存放的对象,而后一次性回收垃圾对象。
这样原来的空闲区间在GC后就变成活动区间,并且内存顺序齐整美观。原来的活动区间在GC后就变成了彻底空的空闲区间,等待下一次GC把活的对象被copy进来。
评价:
复制算法复制移动的对象是【活着的对象】,对于垃圾对象(不被标记的对象)则直接回收。
这个算法则是对上面两个算法的综合结果。也分为两个阶段:
1)标记:这个阶段和标记-清除Mark-Sweep算法同样,遍历GC ROOTS并标记存活的对象。
2)整理:移动全部活着的对象到内存区域的一侧(具体在哪一侧则由GC实现),严格按照内存地址次序依次排列活着的对象,而后将最后一个活着的对象地址之后的空间所有回收。
评价:
【优势】内存空间利用率高,消除了复制算法内存减半的状况;GC后不会产生内存碎片。
【缺点】须要遍历标记活着的对象,效率较低;复制移动对象后,还要维护这些活着对象的引用地址列表。
分代回收算法就是如今JVM使用的GC回收算法。
1)先来看看简单化后的堆的内存结构:
Java堆 = 年老代 + 年轻代 (空间大小比例通常是3:1) 年轻代 = Eden区 + From Space区 + To Space区 (空间大小比例通常是8:1:1)
2)按照对象存活时间长短,咱们能够把对象简单分为三类:
短命对象:存活时间较短的对象,如中间变量对象、临时对象、循环体建立的对象等。这也是产生最多数量的对象,GC回收的关注重点。
长命对象:存活时间较长的对象,如单例模式产生的单例对象、数据库链接对象、缓存对象等。
长生对象:一旦建立则一直存活,几乎不死的对象。
3)对象分配区域
短命对象存在于年轻代,长命对象存在于年老代,而长生对象则存在于方法区中。
因为GC的主要内存区域是堆,因此GC的对象主要就是短命对象和长命对象这类寿命“有限”的对象。
针对HotSpot VM的的GC其实准确分类只有两大种:
1)Partial GC:部分回收模式
2)Full GC:收集整个堆,包括young gen、old gen,还有永久代perm gen(若是存在的话)等全部部分的模式。同Major GC。
3)触发时机
HotSpot VM的串行GC的触发条件是:
young GC:当young gen中的eden区分配满的时候触发。
full GC:当准备要触发一次young GC时,若是发现统计数听说以前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC;或者,若是有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。
并发GC的触发条件就不太同样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen作并发收集。
当须要在堆中建立一个新的对象,而年轻代内存不足时触发一次GC,在年轻代触发的GC称为普通GC,Minor GC。注意到年轻代中的对象都是存活时间较短的对象,因此适合使用复制算法。这里确定不会使用两倍的内存来实现复制算法了,牛人们是这样解决的,把年轻代内存组成是80%的Eden、10%的From Space和10%的To Space,而后在这些内存区域直接进行复制。
刚开始建立的对象是在Eden中,此时Eden中有对象,而两个survivor区没有对象,都是空闲区间。第一次Minor GC后,存活的对象被放到其中一个survivor,Eden中的内存空间直接被回收。在下一次GC到来时,Eden和一个survivor中又建立满了对象,这个时候GC清除的就是Eden和这个放满对象的survivor组成的大区域(占90%),Minor GC使用复制算法把活的对象复制到另外一个空闲的survivor区间,而后直接回收以前90%的内存。周而复始。始终会有一个10%空闲的survivor区间,做为下一次Minor GC存放对象的准备空间。
要完成上面的算法,每次Minor GC过程都要知足:
存活的对象大小都不能超过survivor那10%的内存空间,否则就没有空间复制剩下的对象了。可是,万一超过了呢?前面咱们提到过年老代,对,就是把这些大对象放到年老代。
什么样的对象能够进入年老代呢?以下:
年老代中的对象特色就是存活时间较长,并且没有备用的空闲空间,因此显然不适合使用复制算法了,这个时候使用标记-清除算法或者标记-整理算法来实现GC。负责年老代中GC操做的是全局GC,Major GC,Full GC。
何时触发Major GC呢?
在Minor GC时,先检测JVM的统计数据,查看历史上进入老年代的对象平均大小是否大于目前年老代中的剩余空间,若是大于则触发Full GC。
在搜索扫描和复制过程都是采用单线程实现,适用于单CPU、新生代空间较小或者要求GC暂停时间要求不高的地方。是client级别的默认方式。
在搜索扫描和复制过程都是采用多线程实现,适用于多CPU、或者要求GC暂停时间要求高的地方。是server级别的默认方式。
同时容许多个GC任务,减小GC暂停时间。主要应用在实时性要求重于整体吞吐量要求的中大型应用,即便如此,下降中断时间的技术仍是会致使应用程序性能的下降。
JVM内存调优,主要是减小GC的频率和减小Full GC的次数,Full GC的时候会极大地影响系统的性能。因此在此基础上,更加要关注会致使Full GC的状况。
年老代空间不足
1)分配足够大空间给old gen。
2)避免直接建立过大对象或者数组,不然会绕过年轻代直接进入年老代。
3)应该使对象尽可能在年轻代就被回收,或待得时间尽可能久,避免过早的把对象移进年老代。
方法区的永久代空间不足
1)分配足够大空间给。
2)避免建立过多的静态对象。
被显示调用System.gc()
一般状况下不要显示地触发GC,让JVM根据本身的机制实现。
Minor GC时容易让普通大文件直接绕过survivor进入年老代,从而更容易诱发Full GC。
致使GC频率升高,影响系统性能。
总结一下常见配置。
堆设置 -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:+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数。并行收集线程数。
参考:
http://www.mamicode.com/info-detail-1028149.html
http://www.importnew.com/17770.html
http://www.cnblogs.com/sunfie/p/5125283.html
http://longdick.iteye.com/blog/474764
http://blog.csdn.net/z69183787/article/details/51606410