一、垃圾收集器要面临三个问题
1.哪些内存需要回收?
2.什么时候回收?
3.如何回收?
二、对象已死吗?
不同的GC 会使用不同的引用算法确定对象的引用情况。
引用计数法 :效率高 不能破解双向引用
可达性分析算法 :通过对GC Roots作为根节点,搜索不能到达引用链的对象。java中,栈中的对象,方法区中类静态属性引用的对象,方法区常量引用对象,native方法引用的对象。都可以作为GCRoots
1.reference jdk1.2之后,java对引用概念进行了扩充,分为
强引用:
Object o = new Object() GC不会回收这类对象。
软引用 :
一些有用但非必需的对象
在即将内存溢出异常之前,把这类对象纳入回收范围,进行第二次回收,如果还没有足够的内存,就会抛出内存溢出
弱引用:
比软引用更弱
下一次GC就会被回收,无论内存是否足够
虚引用
最弱的一种引用
不影响生存时间,也无法获得对象实例,唯一目的是这个对象被回收时会收到一个系统通知
2. finalize( ) 逃脱死亡的一次自救(极度不建议使用的方法)
可达性算法中,会经过两次可达性分析
a. 第一次分析后,如果没有与GC Roots相连,则会被标记,并进行一次finalize( ) 方法筛选,如果这个对象覆盖了finalize( ) 方法,就进入 F-Queue队列,并在一个低优先级的Finalizer线程中执行,但这个执行不保证会执行完成,因为这个方法可能执行缓慢,或者死循环。如果这个对象在finalize()中把自己挂在引用链,则就会避免被回收。
b. finalize ( ) 只会被执行一次。
c. 不建议使用这个方法,如果要使用,也要使用 try-finally 去做善后处理。
三、回收方法区
1.方法区被认为是 HotSpot jvm中的永久代,永久代的收集效率比不上新生代带来的内存收益。
2.永久代主要回收:废弃的常量和无用的类
a。比如“abc”没有被任何一个String 对象引用的话,在必要的时候, “abc”会被系统清理出常量池,其他的类,方法,字段的符号引用也类似。在大量使用反射,动态代理,动态生成JSP这类频繁自定义ClassLoader的场景都需要虚拟机具备 类卸载功能,以保证永久代不会溢出。
四、垃圾收集算法
主流的虚拟机都使用分代算法,根据对象存活周期,划分为几块内存,年轻代每次都只有小部分对象存活,使用 标记-复制算法。年老代存活率高,复制成本高,就使用 标记-整理算法来回收。
1.复制算法-
按照 8(Eden):1:1(Survivor)的比例来划分空间,因为大部分的年轻代都是朝生夕死。当标记存活的数据超过survivor的大小时,再使用年老代进行空间担保。
2.标记整理算法-
将存活对象标记后,不进行清理,直接将存活对象向一端移动,然后清理掉边界之外的内存空间。
五、HotSpot 垃圾收集器
不同的厂商和不同的虚拟机都提供了各种收集器。 这里主要讨论HotSpot jdk1.7 u14之后的版本,上面是年轻代收集器,下面是年老代收集器。连线表示可以搭配使用。
image.png
image.png
CMS ,使用初始标记-检查GC Roots 和重新标记都需要 stop the world ,单停顿时间很低。并发标记,并发清除使用多线程去执行。
缺点:1.对CPU资源敏感,当CPU4个以上,且更多时,占用CPU会随CPU数量增加而减少。
2.无法处理浮动垃圾(在CMS 运行过程中产生的新垃圾)这部分垃圾会留在年老代,又由于需要预留一部分年老代内存给应用,所以 CMS不能等到老年代满了才被出发,会设定一个年老代的阈值,超过阈值就会执行CMS,在老年代增长不是太快的情况下通过扩大 -XX:CMSInitiatingOccupancyFraction 的值,可以降低CMS的触发频率。如果CMS运行期间预留的内存无法满足程序需要,就会出现 “Concurrent Mode Failure” 执行 Serial Old,stop world 进行收集。 所以 这个阈值太高很容易出现大量的 Failure ,性能下降。