若是要问Java与其余编程语言最大的不一样是什么,我第一个想到的必定就是Java所运行的JVM所自带的自动垃圾回收机制,如下是我学习JVM垃圾回收机制整理的笔记,但愿能对读者有一些帮助。
<!-- more -->java
如何判断对象已死?有两种算法算法
给对象添加一个计数器,每当有一个地方引用它时,计数器的值就加一,当引用失效的时候,计数器就减一 ,任什么时候刻计数器为0的对象就是不可能再被使用的时候。编程
这个算法看似不错并且简单,不过存在这一个致命伤(当两个对象互相引用的时候,就永远不会被回收)安全
public class Obj{ public Object instance=null; } Obj a=new Obj(); Obj b=new Obj(); a.instance=b; b.instance=a; a=null; b=null;
因而引用计数算法就永远回收不了这两个对象,下面介绍另外一种算法。并发
经过一系列被称为“GC Roots”的对象做为起始点,从这些接点向下搜索,搜索所走过的路径称为引用链,当一个对象与任何一个引用链没有关联的时候则能够被回收。编程语言
Java中,可做为GC Roots的对象包括下面集中学习
不管是计数器算法仍是可达性分析算法,都与“引用”有关,下面是Java中4种引用,强度依次减弱线程
强引用就是咱们平时最熟悉的code
Object obj=new Object();
只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。对象
发生gc的时候,若是JVM内存充足则不回收,用SoftReference类来实现软引用。展现一个例子
SoftReference<Object> softReference=new SoftReference<>(new Object()); System.out.println("before gc "+softReference.get()); System.gc(); System.out.println("after gc "+softReference.get());
如下是输出
before gc java.lang.Object@2752f6e2 after gc java.lang.Object@2752f6e2
能够看到软引用的对象依然还在。
一旦发生gc,不管JVM内存充足与否,都会回收掉,用WeakReference类来实现弱引用。展现一个例子
WeakReference<Object> softReference=new WeakReference<>(new Object()); System.out.println("before gc "+softReference.get()); System.gc(); System.out.println("after gc "+softReference.get());
如下是输出
before gc java.lang.Object@2752f6e2 after gc null
弱引用的对象已经被回收!
虚引用也称为幽灵引用或者幻影引用,是最弱的一种引用,没法经过虚引用来取得一个对象实例,拥有虚引用的对象能够在任什么时候候被垃圾回收器回收。惟一的目的就是能在当这个对象被回收的时候收到一个系统通知,可用PhantomReference类实现虚引用。
垃圾回收大多发生在Heap(堆区),由于在方法区进行垃圾回收效率较低,要断定一个类是不是“无用的类”条件比较苛刻,类须要同时知足下面3个条件才能算是无用的类
首先,GC又分为minor GC 和 Full Gc(也称为Major GC)。Java 堆内存分为新生代和老年代,新生代中又分为1个Eden区域 和两个 Survivor区域。
那么对于 Minor GC 的触发条件:大多数状况下,直接在 Eden 区中进行分配。若是 Eden区域没有足够的空间,那么就会发起一次 Minor GC;对于 Full GC(Major GC)的触发条件:也是若是老年代没有足够空间的话,那么就会进行一次 Full GC。
注意:上面所说的只是通常状况,实际上,须要考虑一个空间分配担保的问题:
在发生Minor GC以前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代全部对象的总空间。若是大于则进行Minor GC,若是小于则看HandlePromotionFailure设置是否容许担保失败(不容许则直接Full GC)。若是容许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若是大于则尝试Minor GC(若是尝试失败也会触发Full GC),若是小于则进行Full GC。
System.gc()就是指的Full GC
可是,具体程序执行到什么位置才会自动gc,这儿提两个概念Safepoint和SafeRegion。
安全点的选定是以程序“是否具备让程序长时间执行的特征”为标准选定的,“长时间执行”最明显的特征就是
对于Safepoint如何在GC发生时让全部线程都跑到最近的安全点上停下来,有两种方案抢先式中断和主动式中断。
抢先式中断:不须要线程执行的代码主动配合,GC发生时,首先把全部线程所有中断,若是发现有线程中断的地方不在安全点上,就恢复线程,让它“跑到”安全点上。(这种方案几乎不多使用)
主动式中断 :当GC须要中断线程的时候,不直接对线程操做,仅仅简单设置一个标志,各个线程执行时主动去轮询这个标志,发现为true就自动挂起,轮询的地方和安全点是重合的。
当线程处于Sleep状态或者Blocked状态,这时候线程没法响应JVM的中断请求,“走”到安全的地方去中断挂起,JVM也显然不太可能等待线程从新被分配CPU时间,这时候就须要安全区域来解决。
当线程执行到Safe Region中的代码时,首先标识本身已经进入了Safe Region,当JVM在发起GC时,就不用管标识本身为Safe Region状态的线程了。在线程要离开Safe Region时,他要检查系统是否完成了整个GC过程,若是完成了,线程就继续执行,不然必须等待直到收到能够安全离开Safe Region的信号为止。
算法分为标记和清除两个阶段,首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象。他的不足主要有两个:
复制算法能够将容量划分为大小相等的两块,每次只使用其中的一块,当一块内存被用完了就将还存活的对象一次复制到另外一块内存上,而后把已经使用过的内存一次清理掉。不过所以内存缩小为原来的一半,代价太高。
如今的商业虚拟机广泛采用这种算法来回收新生代,将新生代分为较大的一块Eden空间和两块较小的Survivor空间,HotSpot默认其比例为8:1:1,使用时,每次使用Eden加上其中一块Survivor,回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor中,最后清理掉Eden和刚才用过的Survivor区。
标记过程与“标记-清除”算法同样,而后让全部存活的对象都向同一端移动,而后直接清理端边界之外的内存。
将Java堆分为新生代和老年代,在新生代中每次垃圾回收都有大批对象死去,少许存活,可使用复制算法,而老年代中对象存活率高没有额外的空间对它进行分配担保,必须使用“标记-整理”或者“标记-清理”算法来回收。
在最近更新的JDK9中,JVM默认的垃圾收集器切换成了G1,那么G1有什么特色呢?总结如下最大的四个特色
《深刻理解Java虚拟机》P61-P84