一.判断对象已死的方法
1.引用计数算法
引用时就加一,引用失效时就减一
但是容易出现两个对象互相引用的时候,就可能无法回收了。
2.可达性分析算法
GC Roots的几种:
1).虚拟机栈中引用的对象
2).方法区中类静态属性引用的对象
3).方法区中常量引用的对象
4).本地方法栈中引用的对象
3.引用的几种类型
强软弱虚四种引用类型
强引用:
只要强引用还存在,就不会被回收
软引用:
在将要发生内存溢出之前,会被回收掉的对象。
弱引用:
比软引用更弱,只能生存到下次垃圾回收之前。
虚引用:
只能在回收的时候收到一个系统通知,是最弱的一种引用。
4.对象的回收
被收集前的两次标记和对象的自我拯救
当一个对象不可达时,JVM会对其进行一系列的判断,首先判断这个对象有没有覆盖finalize方法或者有没有被JVM标记过,如果符合条件则直接回收,如果没有覆盖finalize方法,或者被标记过,则进行第一次标记,将对象放入F_QUEUE队列当中,然后等待JVM的第二次标记,JVM的第二次标记则是判断,在等候的过程中,这个不可达的对象是否重新有了GC ROOT,如果此时它变成了一个可达对象,那么它就完成了一次自我拯救,可以不用被回收,如果依旧不可达,就进行二次标记直接回收。
但是这种自我拯救只有一次机会。
5.回收方法区
永生代主要回收两部分:废弃常量,和无用的类
常量:如果没有任何一个string对象引用这个常量,就判定为废弃常量。
类:满足以下三个条件才可以被回收
1).该类的所有实例都被回收了
2).加载该类的classload都被回收了
3).该类对应的对象都没有被引用了
二.垃圾回收算法
1.分代收集
一般分为新生代收集和老年代收集。
现在一般的收集方法有:minor GC,major GC ,mixed GC(G1收集器)和full GC,minor GC针对一些朝生夕灭的对象,而major GC收集一些难以回收的对象。分代收集可以兼顾时间开销和内存的有效利用。
分代收集可能产生的问题---跨代收集
有可能新生代和老年代又互相引用的对象,不能在收集新生代的时候扫描整个老年代,解决办法是,建立一个记忆集,Remeberd set,这个结构可以把老年代分成若干个小块,表示出老年代的那一部分存在跨代引用,收集新生代的时候,只有包含跨代引用的区域才会被扫描。
2.标记清除(Mark-Sweep)
标记以后进行统一回收,但是容易产生大量不连续内存碎片
3.标记-复制算法
把内存分为一块较大的Eden和两块较小的Survivor,每次使用eden和其中一块survior,回收时将依然存活的对象复制到另一块survivor上,然后将eden和使用过的survivor消除,反复进行这个操作。
4.标记-整理算法
将存活的对象统一移动到一部分,然后再直接清理掉边界以外的内存。
但是移动并且更新所有引用关系负重很大,并且还要暂停所有用户线程“stop the world”,所以停顿时间长就是一个缺点。
二.hotspot算法的细节实现(安全点OOPmap)
1.oopmap
所有的垃圾收集器都会面临查找整个gcroot的关系,并且进行时间停顿的问题,
为了解决这个问题,JVM提供了一个准确式收集的方案,即使用一组OOPMAP的数据结构,当类加载完成以后,可以记录下一个对象什么偏移量是什么数据结构,就会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样就不需要从gcroot一点点查找了。但是如果每个指令都建立oopmap会产生大量的内存开销,所以只在指定的位置(安全点)来建立。
2.安全点
一般安全点的位置产生在指令序列容易出现复用的地方,例如方法调用,循环跳转,异常处理等等。
而且对于安全点的使用,一般是让垃圾收集是让所有的线程都能跑到最近的安全点,在这有两种方案,一种是主动式中断,一种是抢先式中断。
主动中断是设立一个标志位,一直不停的去轮训,当发现中断的标志位变为真,就让当前线程跑到最近的一个安全点中断。
抢先式中断则是在中断以后判断是否在安全点,如果不在的话就重新启动该线程,跑到最近的一个安全点在中断。
3.安全区域
程序发生异常时候,可能无法准确走到安全点,这事可以走到安全区域,安全区域一般是引用关系不会发生变化的一个区域,相当于扩展的安全点。
4.记忆集和卡表
解决跨代引用。
5.并发的可达性分析
如何降低stop the world 的时间:三色标记法
白色;尚未被垃圾收集器访问过,如果分析结束以后还是白色,就说明gcroot不可达,可以被回收
黑色;已经被访问过,说明有对象引用,安全存活
灰色;访问过,但是至少存在一个引用还没有被扫描
三.几种经典的垃圾收集器
1.Serial收集器
新生代收集器:单线程,速度很快,但是时间停顿时间很长
2.Serial-old收集器
老年代收集器,特点同serial
3.Parnew收集器
serial的多线程并行的版本
4.Parallel Old收集器
Parnew的老年代收集器版本
5.Parallel Scanvage收集器
新生代收集器,基于标记复制算法,看重吞吐量。
吞吐量是用户运行代码时间除以用户运行代码时间加上垃圾收集的时间
6.CMS收集器
特点是工作线程可以和垃圾收集线程同时进行,并且基于标记清除算法
优点:并行,且停顿时间短
缺点:容易产生大量内存碎片,和浮动垃圾(当工作进程和收集进程同时进行时产生的无法在本次被收集的垃圾)
7.G1收集器
基于region收集,而且无需区分新生代和老年代,而是面向整个gc堆,寻找垃圾最多的区域进行收集。
这样也可以达到一个可预测停顿时间的目的。并且因为是标记整理算法,所以内存碎片也很少。
8.ZGC和shenandoah收集器
低延迟收集器。
ZGC:染色指针等
四.内存分配策略
1.对象优先在Eden分配
2.大对象直接进入老年代
-XX:PretentureSizeThreshold参数指定超过多大可以直接进入老年代
3.长期存活的对象进入老年代
对象头中有一个对象年龄计数器,超过
-XX:MAXTenuringThreshold=1
的数字时,则会直接进入老年代,此参数默认为15
4.动态年龄判定
当新生代内存不够,虽然没有达到3中设定的参数值,但是也可以直接进入老年代
5.空间分配担保
当新生代内存不够,则需要进入老年代,所以需要minor GC前必须保证老年代最大可用空间大于新生代所有对象大小的总和,jvm可以采用历史平均值来判定,不超过,从而进行一次minorGC,但是如果超过了,就需要full gc了,所以使用平均值判定是存在风险的,但是可以通过以下参数
-XX:handlePromotionFailure设置不允许冒险
网友评论