(一)学习JVM ——运行时数据区域 java
(二)学习JVM —— 垃圾回收机制算法
在Java堆中存放着Java中几乎全部的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是肯定这些对象哪些还存活着,哪些已经可回收。框架
Java采用可达性分析来断定对象是不是存活的,这个算法的基本思路就是经过一系列称为"GC Root"的对象做为起始点,从这些节点向下搜索,走过引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证实这个对象已经能够回收。学习
Java中可做为GC Root的对象有四种:spa
引用的定义是:若是reference类型的数据中存储的数值表明的是另外一块内存的起始地址,就称这块内存表明着一个引用。.net
Java堆引用概念进行了扩展,分为下述4种:线程
真正标记一个对象为可回收,至少要经历两次标记过程:代理
在经历可达性分析后,发现没有与GC Root关联,会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。对没有覆盖或者已经调用过finalize()方法的对象,JVM都不会去执行finalize()方法了。
若是对象有必要执行finalize()方法,那么该对象会被放置到F-Queue队列,并在稍后由一个自动创建的,低优先级的Finalizer线程去执行它。
JVM的共享内存分为堆和方法区,有不少人认为方法区(或永久代)是没有垃圾回收的,JVM规范也确实说过能够不对方法区进行回收,由于性价比较低,通常在堆中,尤为是新生代中,常规应用进行一次垃圾回收能够回收70%~95%的空间,而方法区则否则。
方法区若是进行垃圾收集,主要回收废弃常量和无用的类。
断定常量是不是废弃的常量,是指进入了常量池,可是系统中没有任何一个地方引用了这个字面量,就能够回收。
断定一个类是不是一个无用的类的条件比较苛刻,有3个条件:
是否对类进行回收,能够用-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分配。
老年代对象存活率高,没有额外空间对它进行分配担保,因此采用标记&清理,或标记&整理算法。
前面从理论层面介绍了对象存活的判断方式和垃圾回收算法,而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 —— 垃圾回收机制