一、垃圾回收机制
垃圾回收(Garbage Collection)机制,是自动回收无用对象从而释放内存的一种机制。Java之所以相对简单,很大程度是归功于垃圾回收机制。(例如C语言申请内存后要手动的释放)
优点:简化了开发难度,同时让内存分配更合理。
缺点:屏蔽了底层,同时垃圾回收也会在一定程度上影响程序的性能。
二、如何确定垃圾
可达性分析算法
(1)有了垃圾回收机制,那接下来就是要讨论了的是如何确定内存中那些对象是垃圾。通常JVM会采用可达性分析算法来确定某个对象是否为垃圾对象。可达性分析算法是通过一些列的GCRoot(后续再介绍),通过引用链的方式找到所有的对象,如果一个对象没有与任何GCRoot相连接,则可以认为该对象为垃圾对象。如下图
Object6、7、8、9就是垃圾对象,之后的GC会将其回收。
(2)什么是GCRoot对象呢
GCRoot对象是JVM认为存活的对象,是理解回收的起点。JVM中的GCRoot通常包含以下几种
- 虚拟机栈中引用的对象
- 静态变量
- 常量
- 本地方法JNI引用的对象
(3)使用可达性分析算法可以解决引用计数法带来因为循环引用而导致的内存泄露问题。
例如 A对象引用B,B对线引用A,而两者其实都是null。如果采用引用计数法则不会被认定为垃圾,最终会导致内存泄露;如果内存泄露一直置之不理则可能会引发OOM。
三、垃圾回收算法
1、垃圾回收算法和垃圾回收器的关系
垃圾回收算法一种方法论,用于指导如何回收垃圾;而垃圾回收器则是对不同的垃圾回收算法进行实现,从而回收JVM中的垃圾。简单的来说垃圾回收器是垃圾回收算法的实现和落地。
2、标记清除
标记清除算法,标记清除算法分两步走。第一步:标记垃圾,第二步:清除垃圾。首先垃圾回收器会从GCRoot出发标记出所有的垃圾对象;第二步将标记出的垃圾清除掉。
优点:实现简单,容易理解,能够回收不连续的空间。
缺点:
(1)标记清理分为两步,垃圾回收器需要遍历两次内存,相对来说速度较慢。
(2)标记出来的理解可能分布在内存的不同地方,清除之后容易出现内存碎片。举个例子,清理了100个垃圾对象,每个对象1M,所以能空闲出100M的内存,但是由于垃圾是分布在内存的不同区域,就导致这些内存是不连续的,虽然总共清理出100M,但是此时如果想要存入一个100M的对象是做不到的。
所以,标记清除算法多用于不需要频繁垃圾回收的场景。
3、标记整理
在标记清除的基础上,将存活的对象进行整理,从而减少内存碎片。JVM从GCRoot开始标记出垃圾对象,然后将所有存活的对象压缩的内存的一段,同时记录下边界,然后清除边界之外的垃圾
优点:不存在内存碎片
缺点:效率比标记复制要低一些,因为对象移动了,需要调整虚拟机栈中的引用。
标记整理适合存活对象多,垃圾对象少。
4、标记复制
标记算法需要讲内存一份为二(这里我们简称A和B)。标记复制算法分为三步。第一步通过GCRoot确定那些不是垃圾对象(存活对象),第二步将这存活的对象从内存A区域复制到B区域,第三步清除A所有的对象。
优点:效率相对较高,不会有内存碎片
缺点:内存的利用率低
标记复制算法不适合大量存活对象的场景(例如老年代,大多数对象是存活的),因为大量对象存活意味着大量的对象要被复制,这样效率就很低。反过来,标记复制算法就适合在新生代中,因为新生代中的对象大多数都是"朝生夕死"存活时间很短。
4、分代算法
分代算法是结合了上述几个算法,根据JVM不同区域垃圾对象的特点使用不同的垃圾回收算法。例如新生代的对象特点是,绝大多数的对象"朝生夕死",所以使用标记复制是最合理的。而老年代的对象经过了一次又一次的垃圾回收都是比较稳定的对象,通常大部分对象是存活的,所以更适合使用标记整理算法。