1、垃圾回收
2、什么垃圾
垃圾回收,特指存在于内存中,不会再被使用的对象。
3、识别垃圾
①引用计数法:引用计数法实现很简单,只需要为每个对象配备一个整型的计数器即可。对于对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能在被使用。但是,引用计数器存在两个严重的问题:
-
无法处理循环引用的问题。因此,在java的垃圾回收器中,没有使用这种算法。
-
引用计数器要求在每次因引用产生和消除的时候,需要伴随着一个加法操作和减法操作,对系统性能会有一定的影响。
由于单纯的引用计数算法隐含着循环引用和性能问题,Java虚拟机并未选择此算法作为垃圾回收算法。
②对象是否可达
可达对象:指通过根对象进行引用搜索,最终可以达到的对象。
不可达对象:通过跟对象进行引用搜索,最终没有被引用到的对象。
4、回收垃圾
4.1、标记清除
标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记清除算法可能产生的最大的问题是空间碎片。
4.2、复制算法
复制算法的核心思想是:将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾的回收。如果系统中的垃圾对象比较多,复制算法需要复制的存活的对象数量就会相对较少。因此,在真正需要垃圾回收的时刻,复制算法的效率是很高的。又由于对象是在垃圾回收过程中,统一被复制到新的内存空间中的,因此,可确保回收后的内存空间是没有碎片的。但是,复制算法的代价却是将系统内存折半,因此,单纯的复制算法也难以让人接受。
在Java中新生代串行垃圾回收中,使用了复制算法的思想。将内存区域分为新生代、老年代。新生代分为Eden区、from区、to区3部分。其中from区和to区视为可复制的两块大小相同、地位相等、且可进行角色互换的空间块。from区和to区也称为survivor区。即幸存空间,用于存放未被回收的对象。
在垃圾回收时,Eden区中的存活的对象会被复制到未使用的survivor区中(假设to区),正在使用的survivor区中(假设from区)的年轻对象被复制到to区(大对象,或者老年对象会直接进入老年代,如果to区已满,则对象也会直接进入老年代)。此时,Eden区和from区中的剩余对象就是垃圾对象,可以直接清空,to区则存放此次回收后的存活的对象。这种改进的复制算法,既保证了空间的连续性,有避免了大量内存空间浪费。复制算法比较适合新生代,因为在新生代,垃圾对象通过多于存活对象,复制算法的效果比较好。
4.3、标记压缩
标记压缩算法是一种老年代的回收算法。它在标记算法的基础上做了一些优化。和标记清除算法一样,标记压缩算法也首先需要从根节点开始,对所有可达的对象做一次标记。但之后,它并不是简单地清理未标记的对象,而是将所有存活的对象压缩到内存的一端。之后,清理边界外所有的空间。这种算法既避免了碎片的产生,又不需要两块相同的内存空间,因此,性价比比较高。
4.4、分代算法
分代算法,将内存区间根据对象的特点分成几块,根据每块内存空间的特点,使用不同的回收算法,以提高垃圾回收的效率。
4.5、分区算法
分代算法将按照对象的生命周期长短划分成两部分,分区算法将整个堆空间划分成连续的不同小区间。没法小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,从而产生的停顿也越长。为了更好控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
5、判断可触及性
垃圾回收的基本思想是考察每个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说,此对象需要被回收。但事实上,一个无法触及的对象有可能在某一条件下“复活”自己,如果是这样,那么对他的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全地回收对象。
可触及的:从根节点开始,可以到达的对象。
可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活。
不可触及的:对象的fianlize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会别调用一次。
5.1可触及性强度
强引用:强引用就是程序中一般使用的引用类型,强引用的对象是可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象是软可触及、弱可触及、虚可触及的,在一定条件下,都是可以被回收的。
软引用:软引用是比强引用弱一点的引用类型。一个对象只有持有了软引用,那么当堆空间不足时,就会被回收。所以,软引用对象不会引起内存溢出。每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时,软引用的对象就会进入引用队列。通过引用队列,可以跟踪对象的回收情况。
弱引用:发现即回收。弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用清空如何,都会讲对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并不一定能够很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾收集器回收,便会加入到一个注册的引用队列中。弱引用和软应用一样,在构造弱引用是,也可以指定一个引用队列,当弱引用对象被回收时,就会被加入指定的引用队列,通过队列可以跟踪对象的回收情况。软引用和弱引用都非常适合来保存有些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
虚引用:虚引用是所有引用类型最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。但试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。当垃圾回收器准备回收一个对象时,若发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知引用程序对象的回收情况。由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源的释放操作放置在虚引用中执行和记录。