垃圾的标准
对象被判定为垃圾的标准:
- 没有被其他对象引用
判断对象是否为垃圾的算法:
- 引用计数算法
- 可达性分析算法
引用计数算法
判断对象的引用数量:
- 通过判断对象的引用数量来决定对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
- 任何引用计数 为0的对象实例可以被当作垃圾收集
代码示例
public void ReferenceQuoteCounterProblem(){ MyObject object1 = new MyObject(); //(1) count=1 创建对象 MyObject object2=object1; //(2) count=2 object1= null; //(3)count=1 object2=null; //(4) count=0 该对象实例可以被当作垃圾收集 }
如下图所示,每一根指向或剪断堆中的线代表引用计数器+1或-1
优点:执行效率高,程序执行受影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏
代码示例
public void ReferenceQuoteCounterProblem2(){ MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); object1.childNode = object2; object2.childNode = object1; }
注:该算法机制在jvm不常用
可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收
可以作为GC Root的对象
- 虚拟机栈中引用的对象(栈帧中 的本地变量表)
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 活跃线程的引用的对象
回收算法
垃圾回收算法
- 标记-清除算法(Mark-Sweep)
- 复制算法(Copying)
- 标记-整理算法(Compacting)
- 分代收集算法(Generational Collector)
注:这里只讲最常用的四种回收算法
标记-清除算法(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:对堆内存从头到尾镜像线性遍历,回收不可达对象内存
如下图所示
注:该算法缺点明显,由于标记清除不需要对象的移动,因此会造成多个不连续的碎片。空间碎片太多,当存在分配较大的对象内存(占四个格子)时,没有足够的连续内存,而不得不提前触发另一次GC工作(一直保持clean状态), 导致OOM
复制算法(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。(推倒重建,只需要移动堆顶指针,按顺序分配内容,高效简单)
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象内存清除
如下图所示
优点:
- 解决碎片化问题
- 顺序分配内存,简单高效
- 适用于对象存活低的场景(分代收集-年轻代)
标记-整理算法(Compacting)
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。(标记加移动地址造成成本更高)
- 标记:从根集合进行扫描,对存活对象进行标记
- 整理-清除:移动所有存活的对象,且按照内存地址依次序依次排列,然后将末端内存地址以后的内存全部回收
如下图所示
优点:
- 避免内存的不连续性
- 不用设置两块内存互换
- 适用于存活率高的场景(分代收集-老年代)
分代收集算法(Generational Collector)
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。虚拟机生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。这样可以减少复制对象的时间。(这里只演示JDK8中堆)
- 垃圾回收算法的组合拳
- 按照对象生命周期的不同划分区域,每个区域采用不同的垃圾回收算法
- 目的:提高JVM的回收效率
在Java8及以上版本的虚拟机分代垃圾回收机制中,应用程序可用的堆空间可以分为年轻代与老年代,然后年轻代有被分为Eden区,From区与To区,如下图所示:
GC分类
- Minor GC:发生在年轻代中垃圾收集动作,采用的复制算法。
- Full Gc:发生在老年代中。
年轻代
年轻代是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命,具有有朝生夕死的性质。(尽可能快速地收集掉那些生命周期短的对象)
- 一个Eden区和两个Survivor区组成
- Eden与两个Survivor占比为8:1:1
- 年轻代占整个堆的1/3大小
采用算法
- 复制算法
年轻代三次GC回收流程图如下:
- 当系统创建一个对象时,这个对象的年龄也被确定了(0岁),总是在Eden区操作,当这个区满了,那么就会触发一次YoungGC,也就是年轻代的垃圾回。一般来说这时候不是所有的对象都没用了,所以就会把还能用的对象复制到From区,这时From区的对象增加1(1岁)。
- 这样整个Eden区就被清理干净了,可以继续创建新的对象,当Eden区再次被用完,就再触发一次YoungGC,然后呢,注意,这个时候跟刚才稍稍有点区别。这次触发YoungGC后,会将Eden区与From区还在被使用的对象复制到To区(年龄继续加1)。
- 再下一次YoungGC的时候,则是将Eden区与To区中的还在被使用的对象复制到From区。
- 经过若干次YoungGC后,有些对象在From与To之间来回游荡,这时候From区与To区亮出了底线(默认阈值15),这些家伙要是到现在还没挂掉,对不起,一起滚到(复制)老年代吧。
对象如何晋升到老年代
- 经历一定Minnor次数依然存活的对象
- Survivor区中存放放不下的对象
- 新生成的大对象(-XX:+PretenuerSizeThreshold)
常用的调优参数
- -XX:SurvivorRatio :Eden与两个Survivor的比值,默认 8:1:1
- -XX:NewRatio:老年代和年轻代内存大小比例,默认 2:1
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值
老年代
存放生命周期较长的对象
采用算法
- 标记-清理算法
- 标记-整理算法
触发Full GC的条件
- 老年代空间不足
- 永久代空间不足(JDK8之前存在)
- CMS GC时出现 promotion failed ,concurrent mode failure(可能触发)
- Minor GC 晋升到老年代的平均大小大于老年大剩余空间
- 调用System.gc()(可能触发)
- 使用RMI来进行RPC或者管理的JDK应用,每个小时执行一次Full GC
原文地址:https://www.cnblogs.com/kongliuyi/p/11299320.html