1. 前言java
1.1 概念:清理内存中不会再被使用的对象程序员
1.2 背景:若是内存中的垃圾不被清理,会致使内存溢出算法
1.3 回收区域:方法区和Java堆。【JVM的内存区域主要包括5个区域:Java栈(虚拟机栈)、本地方法栈、PC寄存器、方法区、Java堆。由于Java栈、本地方法栈、以及PC寄存器是线程专有的,当方法结束或者线程结束时,内存就天然就跟着回收了。而Java堆和方法区的内存分配和回收是动态的,正是垃圾回收器因此须要关注的部分。】性能优化
1.4 经常使用的垃圾回收算法:引用计数法(Reference Counting)、标记清除法(Mark-Sweep)、复制算法(Copying)、标记压缩法(Mark-Compact)、分代算法(Generational Collecting)及分区算法(Region)性能
2. 算法演进优化
2.1 引用计数法【Java垃圾回收未采用】spa
2.1.1 思想:对于对象A,若是被引用,A的引用计数器就+1;当引用失效时,A的引用计数器-1.当对象A的引用计数器的值为0,则对象A被回收。线程
2.1.2 缺陷:对象
2.1.2.1 没法处理循环引用的状况。【例如:对象A和对象B相互引用,却没有被其余对象引用。此时,A和B应该被回收。然而,对象A和B的引用计数器却不为0,没法被回收。】 blog
2.1.2.2 每次对象被引用/释放引用的时候,都会伴随着运算操做,对系统性能会有必定的影响。
由于循环引用的对象没法被回收,为了解决该缺陷,因而产生了可达对象的概念。根据可达对象的概念,产生了标记清除法。
2.2 标记清除法【现代垃圾回收算法的基础】
2.2.1 基础:
2.2.1.0 在Java语言中,可做为根节点的对象包括如下几种:
a. Java栈中引用的对象
b. 方法区中静态属性以及常量引用的对象;
c. 本地方法栈中JNI(Native方法)引用的对象。
2.2.1.1 可达对象:经过根对象进行引用搜索,最终能够达到的对象。
2.2.1.2 不可达对象:经过根对象进行引用搜索,最终没有被引用到的对象。
2.2.2 思想:经过标记、清除两个阶段实现垃圾回收
2.2.2.1 标记阶段:根据根节点,标记全部的可达对象【根据根节点标记可达对象后,还会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。在finalize()中没有从新与引用创建关联的对象才会被真正回收。】
2.2.2.2 清除阶段:清除全部的不可达对象
2.2.3 缺陷:
2.2.3.1 产生大量的空间碎片,工做效率低
标记清除法是对一块连续的空间进行回收,回收后的空间是不连续的【如图】。在对象的堆空间分配过程当中,尤为是大对象的内存分配,不连续内存空间的工做作效率要低于连续空间。
由于不连续内存空间的工做效率低,为了解决这个问题,因而想到:新开辟一个工做空间,将可达对象复制到新的内存空间,保证空间使用的连续性。
2.3 复制算法【新生代算法】
2.3.1 思想:将原有的内存空间分为两块,每次使用其中一块。垃圾回收时,将正在使用的内存中的可达对象复制到未使用的内存块中,清除正在使用的内存块中的全部对象,交换两个内存的角色,完成回收。
2.3.2 缺陷:
2.3.2.1 将系统内存折半,占用的系统内存太大。
复制算法的高效性是创建在存活对象少,垃圾对象对象多的前提下。对于存活对象多的状况,复制成本高,须要其它算法来替代。
2.4 标记压缩法【老年代算法】
2.4.1 原理:标记清除算法执行完成后,再进行一次内存碎片整理。
2.4.2 思想:从根节点出发,将全部的可达对象进行标记,而后将标记对象压缩到内存的一端。最后,清除边界以外的全部空间。
在上述算法中,它们都有各自独特的优点,并无一种算法能够彻底替代其它算法。所以,正确的方式是:根据垃圾回收对象的特性,使用合适的算法回收。基于这种状况,产生了分代的思想。
2.5 分代算法
2.5.1 思想:根据对象的特色,将内存区域分红几块;根据每块内存区域的特色,使用不一样的回收算法。
2.5.2 应用:新生代使用复制算法,老年代使用标记压缩法。
既然能够根据对象的生命周期(时间角度)进行回收,那么根据对象的空间分布(空间角度)也能够进行回收。
2.6 分区算法
2.6.1 背景:在相同条件下,堆空间越大,一次GC所用的时间越长,从而产生的停顿也越长。
2.6.2 思想:将堆空间划分红连续的小区域,每一个小区域独立使用、独立回收。
3. 补充
3.1 新生代串行垃圾回收器中,使用复制算法的思想。
3.1.1 基础:新生代分为eden区、from区、to区。其中,from区和to区也被称为survivor区,能够视为用于复制的大小相等、地位相等、且能够角色互换的两个空间块。
3.1.2 思路:垃圾回收时,Eden区和正在使用的survivor区(假设是from区)中的可达对象会被复制到未被使用的survivor区(to区),而后清空Eden区和from区。
3.1.3 备注:from区中的大对象或者老年对象会直接进入老年区;若是to区空间不足,对象也会进入老年区
3.2 复制算法的高效性是创建在可达对象少,不可达对象多的前提下,所以复制算法只用于新生代【新生代,垃圾对象一般会多于存活对象;老年代大部分对象都是存活对象】。老年代采用标记压缩法
3.3 新生代GC与老年代GC对比:
3.4 Java中的引用
判断对象是否存活都与“引用”有关。在Java中,引用分为四种:强引用、软引用、弱引用、虚引用。
3.4.1 强引用
是代码中普片存在的一种引用,形如:Object obj = new Object(); 只要强引用还存在,垃圾回收器就永远不会回收被引用的对象。
3.4.2 软引用
用来描述一些还有用可是非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常(即:内存不足)时,才会被回收。
3.4.3 弱引用
用来描述非必须对象,它的强度比软引用更弱。当GC发生时,必定会被回收(不管内存是否充足)。
3.4.4 虚引用
也叫幽灵引用或幻影引用,是最弱的一种引用。经过虚引用没法建立对象实例。它的做用是可以在这个对象被回收时收到一个系统通知。
3.5 方法区如何判断是否须要回收
方法区主要回收的内容为:废弃的常量和无用的类。对于废弃的常量能够经过引用的可达性来判断,可是对于无用的类则须要经过如下3个条件判断:
3.5.1 该类的全部实例都已经被回收
3.5.2 该类的CLassLoader已经被回收
3.5.3 该类对象的java.lang.Class对象没有在任何地方被使用(即:该类没有被反射访问)
4. 参考文献:
4.1 葛一鸣:《实战Java虚拟机 – JVM故障诊断与性能优化》
4.2 程序员内参 : 《JVM的垃圾回收机制总结》