为何学习垃圾回收机制?java
当须要排查各类内存溢出,内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,咱们就须要对这些“自动化”的技术实施必要的监控和调节。算法
GC算法缓存
引用计数法、标记清除法、标记压缩法、复制算法、并发
引用计数法高并发
是什么?:给对象中添加一个引用计数器,每当有一个地方引用此对象,计数器值加1;当引用失效时,计数器值减1;任什么时候刻计数器值为0的对象就是不可能再被使用的。学习
优势:实现简单,断定率也很高。spa
缺点:很难解决对象之间的相互循环引用。对象
可达性分析法队列
是什么:经过“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC Roots”没有任何引用链相连时,则证实此对象不可用。内存
再谈引用
JDK1.2 以前,Java中的引用定义很传统:若是reference 类型的数据中存储的数值表明的是另一个内存的起始地址,就称为这块内存表明着一个引用。
咱们但愿可以定义描述这样一类对象,内存空间还足够时,则能保留在内存之中;若是内存空间在进行垃圾收集后仍是很是紧张则能够抛弃这些对象。(例如缓存)
4种不一样强度的引用对象:强引用、软引用、弱引用、虚引用。强度依次逐渐减弱。
强引用:Object obj = new Object( ) ;
软引用:描述一些还有用可是非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围之中进行二次回收。
弱引用:描述非必需对象,比软引用更弱,只能生存岛下一次垃圾收集以前。当垃圾收集器工做时,不管当前内存是否足够,都会回收掉只被弱引用关联的对象。
虚引用:彻底不会对其生存时间构成影响,也没法经过虚引用来取得一个对象实例。为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收的时候收到一个系统通知。
生存仍是死亡
即便在可达性分析算法中不可达的对象,也并不是“非死不可”。真正宣告一个对象的死亡,至少要经历两次标记过程:
1:对象进行可达性分析后没有发现与GC Roots相连的引用链,则将会被第一次标记并进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种状况都是为“没有必要执行”。
2:若是对象有必要执行finalize() 方法,那么这个对象将会放置在一个叫作F-Quene的队列中,并在稍后由一个虚拟机会触发这个方法。
回收方法区
Java 虚拟机规范中说过能够不要求虚拟机在方法区实现垃圾收集,并且在方法区中实现垃圾收集的"性价比" 比较低。 在堆中,尤为是在新生代中,常规应用进行一次垃圾收集通常能够回收 70%~90%的空间, 而永久带垃圾收集率远远低于此。
永久带的垃圾收集主要回收两部分:废弃常亮 和 无用的类。
回收废弃常亮:以“abc”字符串常亮为例。“abc”进入了常亮池中,可是当前系统没有任何一个String对象引用“abc”。则对“abc”进行回收。
回收无用的类须要进行下面三次判断:
1:该类全部的实例都已经被回收。也就是Java 堆中不存在该类的任何实例。
2:加载该类的 ClassLoader 已经被回收。
3:该类对应的 java.lang.Class 对象没有在任何地方被引用。没法再任何地方经过反射访问该类的方法。
垃圾回收算法
标记-清楚算法。
分为 "标记" 和 "清除" 两个阶段。
标记 : 标记处全部须要回收的对象。
清除 : 标记完成后统一进行回收。
存在问题:
效率问题:标记和清除过程的效率都不过高;
空间问题:标记清除后会产生大量的不连续的内存碎片,空间碎片太多可能会致使之后程序运行过程当中须要分配较大对象时没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。
复制算法。(未解决标记清除法效率问题而产生)
将内存分为等大小的两块,每次只使用一块。当这一块内存用完了,就将还存活的对象复制到另外一块上面。而后再把已使用过的内存空间一次清理掉。
存在问题:代价是将内存缩小为原来的一半,代价过高。
如今的复制算法主要来回收新生代对象。新生代的对象“朝生夕死”,不须要1:1的比例来划份内存空间,而是将内存分为一大块Eden空间和两块较小的survicor空间(分配比例 8:1:1)。每当回收的时候将Eden空间和已经使用的Survivor 中的对象存放到另外一块 Survivor 中,最后清理掉 Eden 和 Survivor 空间。(这样仅仅只浪费10%)。当Survivor空间不够用的时候,须要依赖其它内存(老年代)进行分配担保。
标记-压缩算法。
复制算法在存在对象较多的时候就要进行屡次复制操做,效率会变低。更关键的是若是不想进行50%的浪费,就要有空间进行分配担保,以应对被使用的内存中全部对象100%存活。因此老年代不能采用这种算法。
标记压缩:标记过程仍然和 "标记清除" 算法同样 , 压缩过程就是让全部可存活的对象移动到内存的一端,而后清理掉边界外的对象。
分代收集算法。
根据对象存活周期不一样将内存划分为新生代、老年代。
新生代:每次都会有大量的对象死去,只有少许存活。采用复制算法。
老年代:对象存活率高、没有额外空间对它进行分配担保。采用 “标记压缩算法”