垃圾收集(Garbage Collection,GC)须要考虑3件事:java
一、哪些内存须要回收算法
二、何时回收spa
三、如何回收线程
Java内存运行时区域中,程序计数器、虚拟机栈、本地方法栈3个区域生命周期与线程相同,这几个区域的内存分配和回收都具有肯定性,不须要考虑回收的问题,在方法结束或线程结束后内存天然跟着回收。Java堆和方法区在运行期才能知道建立哪些对象,这部份内存的分配和回收是动态的,,垃圾收集器关注的是这部份内存。code
在堆中存放着大部分的对象实例,在回收内存前需断定这些对象哪些还存活着。对象
引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时则计数器值减1,任什么时候刻计数器为0的对象就是不可能再被使用的。blog
主流JVM没有选用引用计数算法来管理内存,最主要的缘由是它很难解决对象之间互相循环引用的问题:生命周期
1 public class Test1 { 2 3 public Object instance = null; 4 5 public static void main(String[] args) { 6 Test1 objA = new Test1(); 7 Test1 objB = new Test1(); 8 objA.instance = objB; 9 objB.instance = objA; 10 objA = null; 11 objB = null; 12 } 13 }
若是使用引用计数器,对于objA指向的对象,在代码第6行计数器加1,第9行计数器加1,第10行计数器减1,这样虽然这个对象以后不会再被使用到,但引用计数器没法通知GC收集器回收内存。队列
基本思路:经过一系列的称为"GC Roots"的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,即GC Roots到这个对象不可达时,说明此对象是不可用的。内存
在Java中,可做为GC Roots的对象包括:
一、虚拟机栈(栈帧中的本地变量表)中引用的对象
二、方法区中类静态属性引用的对象
三、方法区中常量引用的对象
四、本地方法栈中JNI(Native方法)引用的对象
在引用计数算法的那个示例中,objA指向的对象一开始是被objA这个引用使用,由于objA是在本地变量表中,因此objA指向的对象是GC Roots的对象。以后objA再也不指向那个对象,那个对象只被objB.instance引用,由于这个对象没有了到GC Roots的引用链,因此能够被回收。
判断对象是否存活与"引用"有关,在JDK1.2以后,Java对引用的概念进行了扩充,将引用按强度依次逐渐减弱分为4种:
一、强引用:在程序中相似"Object obj = new Object()"这类的引用,只要强引用海存在,垃圾收集器永远不会回收掉被引用的对象。
二、软引用:描述一些还有用但并不是必需的对象。在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围进行第二次回收。
三、弱引用:也是描述非必需对象的,可是强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生以前。
四、虚引用:最弱的一种引用关系,没法经过虚引用来取得一个对象实例。
真正宣告一个对象死亡,至少要经历2次标记过程:对象在可达性分析时发现没有与GC Roots相链接的引用链,那它将第一次标记而且进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法。当对象没有finalize()方法或者这个对象的finalize()方法已经被虚拟机调用过,这2种状况为"没有必要执行"。
若是这个对象没必需要执行finalize(),则会在下一次回收时被回收内存。若是被断定为由必要执行,这个对象会放在一个F-Queue队列中,在稍后会由一个虚拟机自动创建的、低优先级的Finalizer线程去执行它。这里执行是指虚拟机会触发这个对象的finalize()方法,但不会等待它运行结束,由于若是一个对象在finalize()方法中执行缓慢或发生死循环,那么F-Queue队列的其余对象可能永久处于等待,致使整个内存回收系统崩溃。稍后GC将对F-Queue中的对象进行第2次小规模的标记,若是对象从新与引用链上的对象创建关联,那么能够避免被回收,finalize()方法是对象逃脱死亡的最后一次机会。
Java虚拟机规范中不要求虚拟机在方法区(或者HotSpot虚拟机中的永久代)实现垃圾收集,并且在永久代的垃圾收集效率是很低的。永久代的垃圾收集主要回收2部份内容:废弃常量和无用的类。回收废弃常量与回收Java堆中的对象相似,当常量没有被引用时就能够被回收。而判断类是否无用须要同时知足下面3个条件:
一、该类全部的实例都已经被回收,即Java堆中不存在该类的任何实例。
二、加载该类的ClassLoader已经被回收。
三、该类对应的java.lang.Class对象没有在任何地方被引用,没法在任何地方经过反射访问到该类的方法。
这个是最基础的收集算法,其余算法都是基于这种思路并对其不足进行改进而获得的。算法思想:首先标记全部须要回收的对象,在标记完成后统一回收全部被标记的对象。这个算法主要有2个不足:
一、效率问题,标记和清除的效率都不高
二、空间问题,标记清除后会产生大量不连续的内存碎片
标记-整理算法是根据老年代的特色提出的:首先标记全部须要回收的对象,而后让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。
根据对象存活周期的不一样将内存划分为几块,通常把Java堆分为新生代和老年代,根据各个年代的特色采用最适当的收集算法,新生代的对象屡次垃圾回收都没有回收对应的内存会移到老年代中,默认新生代和老年代比例是1:2。新生代中每次垃圾收集都只有少许对象存活,选用复制算法。老年代中由于对象存活率高,使用标记-清理或标记-整理算法来回收。
将可用内存按容量大小划分为大小相等的2块,每次只使用其中的一块,当这一块的内存用完了,将还存活的对象复制到另一块上,而后再把已使用的内存空间一次清理掉。这样每次都是对整个半区进行内存是收集,内存分配不用考虑内存碎片等复杂状况。
如今的商用虚拟机使用复制算法来回收新生代,不按照1:1的比例来划份内存空间,而是分为8:1:1的Eden:From Survivor:To Survivor3个空间,每次使用Eden和其中一块Survivor,回收时将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理Eden和用过的Survivor对象。不能保证每次回收都只有很少于10%的对象存活,当Survivor空间不够时,须要依赖老年代进行分配担保。
为何分为2块Survivor对象,若是只有Eden和一块Survivor,按照8:2的比例划分,那么当使用Survivor存储对象的时候,内存很快就满了,须要进行下一次的GC。划分为2块Survivor,Eden能够一直使用。