目录
回收判定
1.引用计数 ReferenceCount
一个对象a被一个变量或对象引用时,a对象对应的引用计数器加一,当前引用失效后计数器减一,若a对象的计数器归0,则代表无引用指向a对象,a为无效对象。
补充:引用计数无法解决对象之间相互循环引用问题
2.可达性 RootSearching --主流
可达性分析中,定main()方法中创建的对象为根节点, 若直接或间接和根节点关联,视该对象为可达,非无效对象。若与根节点无关联性,但其引用计数器非0,实则也是无效对象,需要被垃圾回收。
常见的垃圾回收算法
1.标记清除法
--位置不连续,产生碎片,效率偏低(两次扫描)。
2.拷贝算法
--不产生碎片,浪费内存。
3.标记压缩
--没有碎片,效率偏低,两次扫描,指针需要调整。
回收无效对象过程
判断该对象是否覆盖了finalize()方法
若已覆盖,且该对象的finalize()方法还未被执行过,那么就会将finalize()扔到F-Queue队列中;
若未覆盖或已调用过finalize()方法,则直接释放对象内存
执行F-Queue队列中的finalize()方法
虚拟机会以较低的优先级执行这些finalize()方法们,也不会确保所有的finalize()方法都会执行结束.如果finalize()方法中出现耗时操作,虚拟机就直接停止执行,将该对象清除
对象重生或死亡
如果在执行finalize()方法时,将this赋给了某一个引用,那么该对象就重生了.如果没有,那么就会被垃圾收集器清除.
补充:finalize()方法开销大,不确定性大,不能确定调用对象的顺序,不建议使用。
建议使用try-finally或其他方法回收垃圾
方法区内存回收
1.废弃常量
--和回收废弃对象类似,如常量a不被任何变量或对象引用,则会被清除。
2.废弃的类
--1.判断类的所有对象实例是否都被回收,java Heap中不存在该类的实例
--2.加载该类的类加载器--classLoader被回收
--3.该类的字节码对象java.lang.Class未被其他对象或变量引用。
GC算法
1.标记清除算法
--最基础的收集算法
--缺陷:效率不高,标记回收后存在大量内存碎片,降低空间利用率
1.判断需要回收的数据,然后标记;
2.回收被标记的数据;
2.拷贝算法
将内存等分为两份,设为a,b,存储数据时只在a上存储。当需要回收时,将a上的废弃数据标记,将有用的数据复制到b上,将a上的数据全部回收。
--优缺点:避免了空间中存在内存碎片,但内存少了一半且效率不高。
解决空间利用率
--新生代中,大部分对象存活时间很短,经过一次垃圾回收后只存在少量对象,所以底层中新生代(堆)内存被分为了3块:edan,s1,s2(分配比例8:1:1)。
解决方案:分配内存时只使用edan和s1,当edan和survivor1空间将满时,JVM会发动一次minorGC,清除掉废弃对象,并将所有存活下来的对象移动到Survior2中.接下来就使用Survior2+Eden进行内存分配.通过这种方式,只需要浪费10%的内存空间即可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题
分配担保
--当JVM为一个对象分配内存空间时,发现edan+survior中空闲部分装不下该对象,会触发minorGC,但若minorGC后仍内存空间仍然无法装下该对象,JVM会将edan+survior中的对象移到老年代中,再将新对象存入edan。
--
1、准备在新生代进行minorGC时,首先检查“老年代”最大连续空间区域的大小是否大于新生代所有对象的大小。
2、如果老年代能装下所有新生代对象,minorGc没有风险,进行minorGC
3、老年代无法装下,垃圾收集器进行一次预测:根据以往minorGC过后存活对象的平均数来预测这次minorGC后存活对象的平均数。
(1)以往平均数小于当前老年代最大的连续空间,就进行minorGC,
(2)大于,则进行一次fullGC,通过清楚老年代中废弃数据来扩大老年代空闲空间,以便给新生代做担保。
3.标记整理算法--老年代GC算法
--判断需要回收的数据,然后标记
--将有效对象移动到另一端,然后清理掉端边界外的其他对象。
4.分代收集算法
--老年代中对象存活率高,无额外空间对其分配担保,必须使用"标记-清理"或"标记-整理"算法;
--新生代中存放"朝生夕死"的对象那就用复制算法,只需要付出少量存活对象的复制成本就可以完成收集.
总结:老年代使用标记-清理或标记-整理算法;新生代使用拷贝算法。
内存溢出
--随着老年代内的数据逐渐增多,老年代内存也会放满,字节码执行引擎会执行Full GC。会对整个堆执行对象回收,若未回收到对象,老年代还是满得,同时survivor区还在向老年代内存放数据,就会产生异常数据报错。