垃圾回收机制主要分成垃圾标记和垃圾清除,今天就以这两块为切入点介绍
垃圾标记机制
先介绍一下Java之中的引用
1.强引用
当我们创建一个对象的时候,这个对象就具有强引用,垃圾回收器就算跑出OOM也不会回收它。
2.软引用
如果一个对象是软引用,当内存不够时,会回收这些对象的内存,回收后入股哦还不够,那么跑出OOM。Java提供了SoftRefrernce类来实现软引用。
3.弱引用
弱引用的生命周期更短,垃圾回收器一旦发现了只有弱引用的对象,那么会立刻回收,不管当前内存是否足够。Java提供了WeakReference类来实现弱引用。
4.虚引用
虚引用其实等同于没有引用,再任何时可都有可能被回收。只是一个具有虚引用的对象,被垃圾回收期回收时,会受到一个通知,这也是虚引用的主要作用。
引用计数标记算法
引用计数算法就是说,每当当前对象被引用一次,那么计数就加一,引用失效时计数就减一。当引用计数器的值变成0时,则该对象就不能被使用,变成了垃圾。
问题:引用计数器会产生一个比较尴尬的问题在于,两个对象不被外界引用,反而互相引用,就无法被发现。垃圾回收机制就无法清理他们。因此就产生了以下的另一种垃圾回收机制。
根搜索算法
跟搜索算法的思想就是选定一些对象作为GC Rooots,并组成跟对象集合,然后以这些GC Roots的对象作为起始点,向下搜索。如果目标对象到GC Roots是连接着的,我们既认为该目标是科大的,如果目标对象不可达,那么说明目标对象是可以被回收的。
可以作为GC root对象主要有以下几种:
- Java栈中引用的对象
- 本地方法栈中JNI引用的对象
- 方法区中运行常量池引用的对象
- 方法区中静态属性引用的对象
- 运行中的线程
- 由引导类加载器加载的对象
- GC控制的对象
还有一个问题是,被标记为不可达的对象是否会被立即回收,下面就介绍一下对象的所有状态。
1.创建阶段
2.应用阶段
对象持有强引用,并且可见
3.不可见阶段
对象持有强引用,但是不可见,比如被jni所引用
4.不可达阶段
对象不再持有强引用
5.回收阶段
调用对象的finalize方法,并且垃圾回收器准备对该对象的内存空间进行重新分配
6.终结阶段
对象运行完finalize方法后,仍处于不可达状态,那么等待垃圾回收器回收该对象。
7.对象重新分配
重新分配完对象以后,这个对象等于被回收。
垃圾回收机制
说完标记算法,下面要介绍一下清除算法。
标记清除算法
对内存中的对象进行标记,分成存活对象和可回收对象。如果是可回收对象,那么就对当前对象进行回收。
标记算法主要有两个缺点,一个是标记和清除的效率不高,另外一个是容易产生大量不连续的内存碎片。碎片太多就会导致没有足够的连续内存空间用于存放大对象。从而提前触发一次垃圾回收机制。
复制算法
复制算法主要是为了解决效率不高的问题,它把内存等分成两块,一次只是用一般的内存,清除完内存以后,将当前剩余的对象复制到另一半的内存之中。缺点就是每次使用的内存大小只有原来的一半。但是针对对象较小,以及对象生命周期较短的场景,效率就会异常高。所以复制算法被大量使用于新生代中。
标记压缩算法
标记压缩算法可以理解是标记清除算法的一种升级,在标记逻辑上和标记清除算法是一样的,将对象标记成可回收,和存活对象。但是相比标记清除算法,在清除可回收对象以后,还会进行一个操作,那就是对当前对象进行压缩,把所有对象都压缩到内存的一端。
这样就可以解决标记清除算法会产生大量内存碎片的问题。
分代收集算法
先介绍两个概念
- 新生代
新生代分为三个空间区域,分别是eden,From Surivor 和 To Survivor
每次进行新生代内存回收的时候,都会将eden和From Survivor的存活对象复制到To Survivor。然后将eden和from survivor的对象清空。接着将To survivor的对象复制到From survivor。
当然有两种情况对象会被复制到老年代垃圾收集中。那就是对象存活时间超过一定时间,或者To survivor的对象超过阈值。 - 老年代
相比新生代,老年代收集就是使用上文介绍的标记压缩算法。主要也是用于存放那些周期时间较长的对象。所以他的收集频率较低,耗时较长。