垃圾回收器
Java的内存自动回收技术给开发人员带来了相当大的遍历,让开发人员得以从繁琐的垃圾回收工作中抽身。虽然垃圾回收(GC)已经由JVM实现了自动化,但对其原理的理解还是有必要的,因为当垃圾收集成为系统瓶颈的时候,就需要开发人员对这些技术进行监控和调节。
GC的实现需要考虑三件事:
哪些内存需要回收?
何时回收?
如何回收
1、哪些对象应该被回收?
判定对象是否存活,或者说是否该被回收,通常有以下几种算法:
1.1、引用计数算法
引用计数法是最简单的对象存活判定算法,其原理为:给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1;引用失效时,计数器减1。当引用计数器为0时,便认为对象是不可能再被使用的。
引用技术算法原理很简单,对于大部分的对象存活判定效率也高,但却很难处理循环引用的情况,所以主流的JVM虚拟器未使用这个算法进行垃圾回收。
1.2、可达性分析算法
可达性分析算法将一系列称为“GC Roots”的对象作为起始点,将关联的对象连成“引用链”。若一个对象到GC Roots不可达,则认为此对象是可回收的对象。
在java中,可作为GC Roots的对象包括以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法去中常量引用的对象。
本地方法区中JNI(Native方法)引用的对象。
1.3、对象引用的4种类型
java将引用分成四种类型:强引用、软引用、弱引用、虚引用。
强引用普遍存在,如new Object()。只要前引用还存在,垃圾收集器便不会回收被引用的对象
软引用用来描述有用但非必须的对象,但系统将要出现内存逸出时,JVM会对此类引用进行回收,如果回收后还是内存不足,才会抛出内存逸出异常。
弱引用也是描述非必须对象,强度再弱于软引用。无论内存是否充足,弱引用都是被回收的对象。
虚引用,也称幽灵引用或幻影引用,它不影响对象的生存时间,其存在的唯一目的是能在对象被回收时收到一个系统通知。
1.4、宣告对象“死亡”的两次标记
当初步判断对象可回收时,将被第一次标记,并做一次筛选:对象是否覆盖finalize方法,或该方法是否被系统执行过(每个对象的finalize方法只可被系统调用一次)。如果该方法未执行,则系统将该对象放置于队列F-Queue中,由虚拟机建立低优先级的线程逐个触发F-Queue队列中对象的finalize方法。若对象在finalize中重新建立引用,则可逃脱被回收的命运,并从“即将回收”的集合中移除,否则会打上第二次标记。被打上两次标记的对象,基本就被系统回收了。
1.5、回收方法区
方法区通常存储常量、类的信息等“永久代”数据,这些数据大都需要长时间驻留在内存中,即便如此,方法区仍需要进行垃圾收集。永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
判定一个常量是否是“废弃常量”,只需要判断有没有其他地方引用了这个常量,没有引用则此常量会被系统清理出常量池,常量池中的其他类(接口)方法、字段的符号引用类似。而判定一个类是否是“无用的类”,则需要同时满足以下3个条件:
该类的所有实例已被回收
加载该类的ClassLoader已被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
2、垃圾收集算法
2.1、标记-清除算法
原理:标记所有要回收的对象,标记完成后统一回收。
缺点:标记和清除的效率都不高;标记清除后会产生大量不连续的内存碎片。
2.2、复制算法
原理:将内存分成两半,一次只用一半。当一半区域内存用完时,将仍存活的对象复制到另一半,复制完成后将旧区域整个清除。
优点:实现简单,运行高效,无需考虑内存碎片问题。
缺点:实际使用内存减半,空间代价高昂。对于老年代区域采用此算法效率低下。
2.3、标记整理算法
原理:在标记-清理算法基础上优化,标记完成后将存活对象向一侧移动,形成连续的区域,然后将区域边界之外的内存全部清掉。
2.4、分代收集算法
原理:分代收集算法是目前商业上广泛采用的垃圾收集算法。它将内存区域分成新生代与老年代,对新生代使用复制算法,对老年代使用”标记-清理“或”标记-整理“算法。