(二)学习JVM —— 垃圾回收机制

(一)学习JVM ——运行时数据区域 java

(二)学习JVM —— 垃圾回收机制算法

(三)学习JVM —— 垃圾回收器 安全

(四)学习JVM —— 内存分配与回收策略 数据结构

对象什么时候可被回收?

在Java堆中存放着Java中几乎全部的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是肯定这些对象哪些还存活着,哪些已经可回收。框架

可达性分析

Java采用可达性分析来断定对象是不是存活的,这个算法的基本思路就是经过一系列称为"GC Root"的对象做为起始点,从这些节点向下搜索,走过引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证实这个对象已经能够回收。学习

Java中可做为GC Root的对象有四种:spa

  1. 虚拟机栈中引用的对象;
  2. 方法区中静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI(Native方法)引用的对象;

引用的种类

引用的定义是:若是reference类型的数据中存储的数值表明的是另外一块内存的起始地址,就称这块内存表明着一个引用。.net

Java堆引用概念进行了扩展,分为下述4种:线程

  • 强引用(Strong Reference):只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象;
  • 软引用(Soft Reference):将要发生内存溢出以前,将会把这些对象列入回收范围之中进行第二次回收。若是此次回收尚未足够的内存,才会内存溢出;
  • 弱引用(Weak Reference):弱引用只能生存到下一次垃圾回收发生以前。
  • 虚引用(Phantom Reference):虚引用(幻影引用)惟一的目的就是能在这个对象被回收时收到一个系统通知。

回收前的细节

真正标记一个对象为可回收,至少要经历两次标记过程:代理

在经历可达性分析后,发现没有与GC Root关联,会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。对没有覆盖或者已经调用过finalize()方法的对象,JVM都不会去执行finalize()方法了。

若是对象有必要执行finalize()方法,那么该对象会被放置到F-Queue队列,并在稍后由一个自动创建的,低优先级的Finalizer线程去执行它。

方法区怎么回收?

JVM的共享内存分为堆和方法区,有不少人认为方法区(或永久代)是没有垃圾回收的,JVM规范也确实说过能够不对方法区进行回收,由于性价比较低,通常在堆中,尤为是新生代中,常规应用进行一次垃圾回收能够回收70%~95%的空间,而方法区则否则。

方法区若是进行垃圾收集,主要回收废弃常量和无用的类。

断定常量是不是废弃的常量,是指进入了常量池,可是系统中没有任何一个地方引用了这个字面量,就能够回收。

断定一个类是不是一个无用的类的条件比较苛刻,有3个条件:

  1. 该类全部的实例已经被回收,堆中不存在该类的任何实例;
  2. 加载该类的ClassLoader已经被回收;
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,没法在任何地方经过反射访问该类的方法;

是否对类进行回收,能够用-Xnoclassgc参数进行控制,还能够在Product版虚拟机中,用-verbose:class以及-XX:+TraceClassLoading查看类加载和卸载信息。在FastDebug版虚拟机中用-XX:+TraceClassUnLoading参数查看。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都须要虚拟机具有类卸载的功能,以保证方法区不会溢出。

对象回收的算法

垃圾回收的经常使用算法有三种,分别是标记&清除算法、复制算法和标记&整理算法。

标记&清除算法

标记&清除(Mark&Sweep)分为两个阶段,标记和清除。首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象,该算法的主要弊端在于,清除以后会产生大量不连续的内存碎片,碎片太多会致使之后在运行过程当中须要分配较大对象时,没法找到连续的内存空间,而不得不提早出发另外一次垃圾收集。

复制算法

复制(Copying)算法,能够将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还存活着的对象复制到另外一块,而后把已经使用过的内存一次清理掉。只是这种作法将内存缩小到了原来的一半,成本高了一点。

根据IBM公司专门研究代表,新生代中有98%的对象时朝升西落的,因此,并不须要按照1:1的比例来划份内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一个Survivor空间。

当回收时,将Eden和Survivor中还存活的对象,一次性的复制到另外一块Survivor中,而后清理掉Eden和刚才使用的那个Survivor空间。JVM默认采用8:1:1的方式分配Eden和两个Survivor。

当复制过程当中发生Survivor不够用时,须要依赖其余内存(这里指老年代)进行分配担保(Handle Promotion),将这些对象直接分配到老年代。

标记&整理算法

标记&整理(Mark&Compact),标记过程与标记清理同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。 

分代回收算法

当前商业虚拟机的垃圾收集都采用分代收集(Generational Collection),根据对象存活周期的不一样将内存划分为几块。通常是把Java堆分红新生代和老年代。

新生代对象存活率低,选用复制算法,将内存以8:1:1分配。

老年代对象存活率高,没有额外空间对它进行分配担保,因此采用标记&清理,或标记&整理算法。

HotSpot虚拟机回收算法的实现

前面从理论层面介绍了对象存活的判断方式和垃圾回收算法,而JVM在实现这些算法时,必须对算法的执行效率有严格的考量,才能保证JVM高效运行。

枚举根节点

JVM在进行可达性分析时,并不会真的从每个GC Root进行引用链检查,如今不少应用仅仅是方法区就有数百兆,若是要逐一检查,必然会消耗不少时间。

另外,可达性分析还体如今GC停顿上,为了确保分析准备,不能够出如今分析过程当中对象引用关系还在不断变化的状况,GC停顿还被Sun公司成为称为Stop The World。

HotSpot实现中,使用一组OopMap的数据结构来帮助快速检查引用链,在类加载完成时,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程当中,也会在特定的位置记录栈河寄存器中那些位置是引用。这样,GC在扫描时就能够直接得知这些信息了。

可是可能致使引用关系变化的指令很是多,若是为每一条都生成对应的OopMap,那将须要大量额外的空间,这样GC的成本会变得很高。下面要介绍的安全点则解决了这个问题。

安全点

程序执行时并不是在全部位置均可以停下来GC,只有在达到安全点(Safepoint)时才能暂停。

安全点的选定不能太多,也不能太少,它的选定基本上是以程序“是否具备让程序长时间执行的特征”为标准来选定的,长时间执行最明显的特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,具备这些功能的指令才会产生安全点。

另外一个要考虑的问题是,如何在GC发生时,让全部线程都跑到最近的安全点停顿下来。

目前,基本上全部的虚拟机都采用主动式中断的思想,当GC须要中断线程的时候,轮询一个标志,发现中断标志为true时,就本身中断挂起,轮询标志的地方河安全点是重合的。

安全区域

程序不执行的时候,就是没有分配CPU时间时,线程没法响应JVM的中断请求,这时就须要安全区域(Safe Regin)来解决。

安全区域是指一段代码片断中,引用关系不会发生变化。在这个区域GC都是安全的。

线程进入安全区域时会进行标记,离开时要检查本身是否完成了根节点枚举,若是完成了就继续工做,不然要等到能够安全离开的信号为止。 

(一)学习JVM ——运行时数据区域

(二)学习JVM —— 垃圾回收机制

(三)学习JVM —— 垃圾回收器

(四)学习JVM —— 内存分配与回收策略

相关文章
相关标签/搜索