架构师训练营 第九周 课后练习
作业
请简述JVM垃圾回收原理。
定义
垃圾收集(Garbage Collection,简称GC)。
内存动态分配与内存回收技术已经相当成熟,为什么我们还要去了解垃圾收集和内存分配?
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。
垃圾收集需要完成的三件事情:
哪些内存需要回收?
什么时候回收?
如何回收?
哪些内存需要回收?
Java内存运行时程序计数器、虚拟机栈、本地方法栈3个区域随线程而生而灭,栈中的栈帧随着方法的进入和退出有条不紊地执行着出栈和入栈操作。这几个区域的内存分配和回收都具有确定性,当方法结束或者线程结束的时候,内存就随着回收了。
而Java堆和方法区这两个区域则有着很显著的不确定性,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少对象,这部分内存的分配和回收是动态的,垃圾收集器所关注的正是这部分内存该如何管理。
什么时候回收
JVM垃圾回收就是将JVM堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。
可达性分析算法
算法思路
当前主流的商用程序语言的内存管理子系统,都是通过可达性分析算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为”GC Roots“的根对象作为起始节点集,从这些结点开始,根据引用关系向下搜索,搜索过程中所走的路径称为”引用链“,如果某个对象到GC Roots之间没有任何引用链相连,则证明此对象是不可能再被使用的。
垃圾对象的识别过程
从线程栈中的局部变量或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他的对象,继续进行标记,所有被标记的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。
例外-逃逸
即使在可达性分析算法中判定为不可达对象,也不是”非死不可“的。如果对象在进行可达性分析发现没有与GC Roots相连接的引用,那它会被第一次标记,随后进行一次筛选,筛选的条件是是此对象是否有必要执行finalize()方法。如果对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为没必要执行。如果对象被判定为有必要执行finalize()方法,那么该对象将会被放在一个名为F-Queue的队列中,并且在稍后的由一条由虚拟机自动建立、低调度优先级的Finalizer线程去执行它们的finalize()方法,finalize()方法是对象逃脱死亡命运的最后一次机会。收集器将会对F-Queue中的对象进行第二次小规模的标记,只要重新与引用链上任何一个对象建立关系,它就在被收集前成功逃逸了。
垃圾收集算法
分代收集
垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象根据其年龄分配到不同的区域之中存储。
如果一个区域中大多数的对象都是朝生夕灭,那么把它们集中在一起,每次回收只关注如何保留少量的存活对象,而不是去标记那些大量的将要被回收的对象,这样可以以较低代价回收到大量的空间。
如果剩下的都是难以消亡的对象,那么将他们集中到一起,虚拟机可以使用较低的频率来回收这个区域,这样就兼顾了垃圾收集的时间消耗和内存空间的有效利用。
设计者一般会把Java堆划分为新生代和老年代两个区域。在新生代中,每次垃圾收集时会有大批对象死去,而每次回收后存活的少量对象,会逐步晋升到老年代中存放。
标记清理算法
将垃圾对象占据的内存清理掉,其实JVM并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。
标记压缩算法
从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。
标记复制算法
将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。
垃圾回收器算法
串行Serial回收器
这个收集器是一个单线程工作的收集器,它在进行垃圾收集的时候,必须暂停其他所有的工作,直到它手机结束。
缺点
在用户不可知、不要可控的情况下把用户的正常工作线程全部停止,对于很多应用来讲是不可以接受的。
优点
简单而高效,对于内存资源受限的环境,内存消耗最小;没有线程交互的开销,专心做垃圾收集效率较高。
并行Parallel回收器
并行(Parallel)描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作。
并行回收器的特点是它的关注点与其他的收集器不同,它的目标是达到一个可控制的吞吐量。
并发回收器CMS
并发(Concurrenct)描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能相应服务的请求,但是由于垃圾收集器占用了一部分的系统资源,此时应用程序的处理的吞吐量将会受到一定的影响。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它的运作过程相对复杂,包括如下的四个步骤:
1)初始标记
2)并发标记
3)重新标记
4)并发清除
优点
并发收集、低停顿
缺点
1)对处理器资源非常敏感,在并发阶段,虽然不会导致用户的线程停顿,却会因为占据了一部分的资源而导致应用程序变慢,降低吞吐量。
2)由于CMS无法处理“浮动垃圾”有可能出现并发模式失败而导致一次完全的“Stop The World”的Full GC的出现。
3)收集结束会出现大量的空间碎片。
G1(Garbage First)收集器
G1收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局模式。
G1不再坚持固定大小以及固定数量的分代区域划分,而是将连续的Java堆划分为多个大小相等的独立区域(Region),每一个区域都可以根据需要扮演新生代的Eden空间、Survivor空间或者老年代空间。
收集器能够对扮演不同角色的Region采用不同的策略去处理。
更具体的处理思路是让G1收集器去跟踪各个Region中的垃圾堆积的价值大小,然后在后台维护一个优先级列表,优先处理回收价值收益最大的Region,也就是Garbage First名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
参考:
架构师训练营第六章课件-李智慧
深入理解Java虚拟机-周志明
版权声明: 本文为 InfoQ 作者【且听且吟】的原创文章。
原文链接:【http://xie.infoq.cn/article/e8904a00d2802989d963a9c39】。未经作者许可,禁止转载。
评论