您的位置:首页 > 编程语言 > Java开发

Java补完之垃圾回收GC机制学习笔记

2017-03-04 15:39 701 查看

前言

一般情况下Java开发进阶的标志:1.将设计模式合理应用到开发中;2.良好的代码编写风格;3.熟练掌握自动化测试技术;4.熟悉代码重构的过程和方法;5.掌握多线程开发;6.对于虚拟机(JVM)机制的深入理解。所以我在面试高级开发人员是一般会围绕这几个问题来提问。

之前已经有了一篇介绍多线程开发的文章,由于篇幅有限,这篇将着重于介绍Java垃圾回收(Garbage Collection,GC)机制。

习惯问为什么是一个好习惯,下面就先介绍为什么要了解垃圾回收机制。

为什么

有不少人可能觉得java有自动垃圾回收机制,一切听JVM的安排,我们完全可以不用关心这个问题,

确实Java的GC已经有了相当长的时间了,而且也确实日趋完善,在大部分情况下是可靠的,但在实际工作中还是可能会出现垃圾无法及时自动回收导致的OOM问题或Stop The World问题,而且这种问题一般都很难定位和解决。通过了解Java垃圾回收机制可以帮助我们更好的防范于未然和解决各种OOM问题。

我们都知道Java会为我们自动动态分配内存和自动清理内存,当初幼稚的我再也不用担心内存溢出了,好吧那是后话,我们首先要回想一下什么样的数据或内存会被JVM自动回收呢,JVM有没有可能把我们要用的东西清理了呢,再进一步要是我们去实现GC的话我们会怎么做呢?

下面我们就开始介绍什么样的数据会被自动清理。

内存回收的依据

在主流的商用语言的主流实现中确定内存可否被回收都是通过可达性分析判定对象是否存活的。该算法的基本思想是通过一系列的“GC Roots”的对象作为起始点,从这些起点开始向下搜索,搜索所走过的路径被称为引用连,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

需要注意的是GC Roots并不是唯一的节点,其可以包括下面几种来源:

- 虚拟机栈中引用的对象

- 方法区中静态属性引用的对象

- 方法区中常量引用的对象

- 本地方法中JNI引用的对象

上面中的引用又可以分为强引用、软引用、弱引用和虚引用;

强引用最为常见,
Object obj=new Object()
就是强引用,只要还存在垃圾回收器就不会回收被引用的对象。

软引用是描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统要发生OOM之前,将会把这些对象列进回收的范围进行第二次回收。

弱引用也是描述非必需的对象,但它的强度还会再弱一些,被弱引用关联的对象只能生存到下次GC之前。

虚引用也称为幽灵引用或幻影引用。一个对象是否有虚引用的存在并不影响其是否会被回收,区别只是如果有虚引用则会在它被回收时收到一个系统的通知。

还有一个问题就算一个对象已经被判定为“不可达”也不一定会被回收,特例就是若这个对象有finalize()方法,并且这个方法中又重新与”GC Roots”建立了联系则这个对象就能死里逃生,但需要注意的是finalize()方法只能运行一次,如果被判定为两次则必死无疑。

从上面的判定标准可以看到,JVM是异常谨慎的,几乎不可能会将有用的数据清理掉。

GC算法

标记-清除算法

该算法是最简单的算法,主要分为两个阶段:1.标记出需要回收的对象;2.在标记完成后统一回收所有被标记的对象。具体过程如下图所示(图片转载自网络)。



但它存在两个不足:

1.效率问题:标记和清楚的两个过程效率都不高,需要历遍两次。

2.空间问题:标记清楚后会产生大量不连续的内存碎片,内存碎片太多时会导致后面无法存放较大的对象。

复制算法

该算法将内存分为大小相等的两块,每次只是用其中的一块,当这一块内存用完了,就将还存活的对象复制到另一块上面,并发已使用过的内存一次性清理掉。这样每次都是对半个内存进行回收,而且也不会产生内存碎片的问题。在实现层面上,只要一动堆顶指针,按顺序分配内存即可,实现简单,运行高效,但内存只能利用一半。



这个算法利用不一样的思路一次性解决了标记-清除的全部问题,但牺牲了很大的内存空间,这个牺牲确实很大,但其勇敢的尝试为GC算法的进步卖出了重要的一步。

后面会有GC实现对应这个算法,其实不一定需要牺牲一半的内存,根据对象在内存中的生存周期的分布特性可以减少牺牲的内存,这也就是后面的新生代,老年代和survivor空间的由来。

标记-整理算法

由于复制算法在对象存活率较高时效率会很低,因此又有人提出了标记-整理算法来解决这类内存的回收问题。

这个算法跟最原始的标记-清除的标记阶段一样 ,但不一样的是在清楚阶段是,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界意外的内存。



分代算法

当前商业JVM的GC实现都是采用分代算法,即将内存分成几个区域,每个区域采用不同的回收算法处理,分区标准就是根据对象的存活周期长短来区分。在生命周期短的区域使用复制算法,在生命周期长的区域使用标记-整理或标记-清理算法。

了解了以上的GC算法,以后遇到有人希望你说一下垃圾回收机制的时候,你可以反问一句你希望听哪一种GC算法,保证一大半的已经懵逼了。

GC实现的基础知识

内存的分区

为提高垃圾回收的效率,内存按照对象的生命周期的长短进行分区,存放生命周期长的区域被称为老年代,存放生命周期短的区域被称为新生代。

Stop the world的由来

Stop the world是指进行GC时必须停顿所有Java执行线程的现象。JVM中除了GC的所有工作都停止了,为什么会有这个现象呢,主要是为了实现准确的GC,不会有人希望自己的数据会有一百万分之一的几率被GC误清除吧,需要进行全局性的可达性分析即枚举GC Roots。虽然有无数人月被投入到如何消除Stop the world现象但截至目前为止所有GC都需要一定时间的停顿完成GC Roots的枚举,但在合理设置和开发的前提下,这个时间可以被降低到一个可接受甚至是用户无法察觉的程度。

内存分配策略

上文已经介绍过了,内存中对象的生存时间并不一致,研究表明大部分对象都是“朝生夕死”的,所以并不需要1:1的分配老年代和新生代。

将内存分为一块较大的老年代Survivor和两块较小的新生代Eden,每次使用Eden和一块Survivor。当回收时,将Eden和Survivor还存活的对象一次性的复制到另一块Survivor中,会后清理掉Eden和用过的Survivor空间。默认Eden和Survivor为8:1.

根据前面的统计情况,一般情况下空间是充足的,但特殊情况时会导致Survivor空间不足以保存现有数据,这时就需要Eden进行腾出一部分空间来保存对象了。这个策略 就是拿空间换时间,若空间不足再拿时间换空间,这个策略在一般情况下非常有效,但在不好的内存配置情况或特殊业务场景下可能导致系统性能瓶颈的出现。

内存分配策略:

对象优先在新生代Eden分配

大对象直接进入老年代

长期存活的对象进入老年代

GC的实现

Serial收集器

最早期的GC实现,单线程的GC收集器,它在GC时会导致其他线程停止,从而造成Stop the world的发生。

JVM运行在Client模式下的默认新生代收集器,简单而高效,在回收空间有限时,停顿时间完全可以接受。还是那句话没有最好的,只有最合适的。

ParNew收集器

ParNew实际上就是Serial的多线程版本,Sever模式下的默认新生代收集器,可以与后文的CMS收集器配合使用以发挥更强大的组合作用。作为多线程版本的ParNew在多CPU的表现要好于Serial。

Parallel Scavenge收集器

新生代收集器,使用复制算法。这个搜集器的特色是能通过控制吞吐量(即能够调整垃圾回收的所占用的时间)来调整停顿时间,即通过降低吞吐量来降低每次垃圾回收所停顿的时间。

由这个收集器可以看到一切都是针对应用场景的,没有最优只有最合适。

Serial Old收集器

是Serial的老年代版本,单线程,使用标记-整理算法。主要是搭配Paralled Scavenge或作为CMS收集器的后备方案。

Parallel Old收集器

Parallel Old是Parallel Scavenge的老年代版本,在注重吞吐量以及CPU资源敏感的场合,都可以有优先考虑Parallel Scavenge和Parallel Old的组合。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿为目标的收集器,比较适合重视服务响应速度应用场景。基本是给予标记-算法,但具体过程可以分为:

初始标记,标记GC Roots直接关联的对象,非常快,但可能存在Stop the world

并发标记 ,耗时较长,但可以与用户线程一起工作

重新标记,纠正可能由于并发标记过程中可达性变化的对象,可能存在Stop the world

并发清除,耗时较长,但可以与用户线程一起工作

这个收集器巧妙的设计使其可以尽可能的减少停顿时间,并将耗时的工作与用户线程同时进行。

但CMS也存在一定的问题

对CPU资源非常敏感

无法处理浮动垃圾

可能产生大量的碎片

G1收集器

G1(Garbage First)收集器是目前最前沿的收集器,面向服务端应用。

特点如下

并行与并发:使用多CPU减少Stop the world时间

分代收集:不同的方式管理老年代和新生代

空间整合:标记-整理的思路,无碎片化问题

可预测的停顿:有模型可以预测停顿时间

它的过程与CMS类似也是初始标记,并发标记,最终标记,筛选回收。

主要参考资料

1.《深入理解Java虚拟机》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java