JVM之垃圾回收

市面上有关JVM垃圾回收的文章很多,有些是针对垃圾收集器,有些是介绍垃圾回收算法,也有些各方面都有涉及。本文希望能做一个比较全面的总结,最关键的是形成自己的语言,有自己的理解和沉淀。

一、为什么需要垃圾回收

大家都知道,java语言的内存是动态分配的,不像C++语言还需要开发者专门干预,在一定程度上可以提高开发效率。那么大家可能疑问:既然不需要我们开发者关心,我们为什么还要讨论?作为一个程序语言的驾驭者,必然需要掌握该语言的方方面面,包括底层的原理和机制。
Java语言使用了内存动态分配和垃圾回收技术,掌握这些不仅可以提高自己的逼格,而且为后续的JVM调优打下扎实的基础,让自己离架构师更近一步。

二、哪些内存需要回收

JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法。

1、引用计数法

在这种方法中,堆中每个对象实例都有一个引用计数。任何引用计数器为0的对象实例可以被当作垃圾收集。
引用计数是垃圾收集器中的早期策略。该方法看似很实用,但是解决不了循环引用的问题(比如循环链表)。

2、可达性分析算法

从一个节点GC ROOT开始,寻找对应的引用节点,然后寻该引用节点的引用节点,当所有的引用节点寻找完毕之后,剩余没有被引用的节点将会被判定为是可回收的对象。

3、引用分类

在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用四种,这四种引用强度依次逐渐减弱。垃圾回收算法都是基于强引用而言的。

三、垃圾回收算法

1、标记-清除算法

标记-清除算法从根集合(GC Roots)开始扫描,对需要继续存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。

图片描述

标记-清除算法只需对不需要存活的对象进行处理,在存活对象比较多的情况下极为高效,但是会造成内存碎片。

2、复制算法

复制算法的提出就是为了解决内存碎片的问题,如下图所示。复制算法虽然解决了内存碎片的问题,但是浪费一半内存。另外在对象存活率很高的时候,复制成本会非常高。

图片描述

3、标记-整理算法

标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:

图片描述

4、分代收集算法

一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
新生代采用复制算法;老年代采用标记-整理算法。

其中年轻代内存分配过程如下:
1) 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡;
2) 最初一次,当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0;
3) 下次Eden区满了,再执行一次Minor GC,将消亡的对象清理掉,将存活的对象复制到Survivor1中,然后清空Eden区;
4) 将Survivor0中消亡的对象清理掉,将其中可以晋级的对象晋级到Old区,将存活的对象也复制到Survivor1区,然后清空Survivor0区;
5) 然后跳到第三步,当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制)之后,仍然存活的对象,将被复制到老年代。

图片描述

四、垃圾收集器

图片描述

1、新生代收集器

1) Serial收集器
单线程收集器
2) ParaNew收集器
Serial收集器的多线程版,关注缩短垃圾收集时间。(使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。)
3) Parallel Scavenge收集器
关注CPU吞吐量,即运行用户代码的时间/总时间。(使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收;使用-XX:MaxGCPauseMillis设置GC的最大停顿时间;使用-XX:+UseAdaptiveSizePolicy可以进行动态控制Eden/Survivor比例,老年代对象年龄,新生代大小等。)

2、老年代收集器

1) Serial Old收集器
单线程收集器
2) Parallel Old收集器
Parallel Scavenge收集器的老年代版本(使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge +Parallel Old组合收集器进行收集。)
3) CMS收集器
多线程,优点是并发收集(用户线程可以和GC线程同时工作)

3、G1收集器

特性:
1) 首先收集尽可能多的垃圾(Garbage First)
内部采用了启发式算法,找出具有高收集收益的分区进行收集。
2) 并行和并发
和CMS相似,可以做到用户线程和GC线程同时工作。
3) 内存布局调整
将整个堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。每个分区都可能随G1的运行在不同代之间前后切换。
G1划分了一个Humongous区,它用来专门存放巨型对象。
4) GC模式
G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。

图片描述

五、引用文献

[1] https://blog.csdn.net/ft30597...
[2] https://www.cnblogs.com/1024C...
[3] https://blog.csdn.net/x_i_y_u...
[4] https://www.jianshu.com/p/e99...
[5] https://blog.csdn.net/foolish...
[6] https://blog.csdn.net/coderli...
[7] https://www.cnblogs.com/ASPNE...


架构进阶之路
9 声望0 粉丝