Hi! 我是小小,今天是本周的第六篇,本篇将会着重的讲解关于Java垃圾回收机制。

垃圾回收的意义

Java语言的显著特点就是引入了垃圾回收机制,使得C++程序猿最为头疼的内存管理问题就直接解决掉了。这使得Java程序猿编写代码的时候,不需要直接考虑内存管理,垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。

垃圾回收机制中的算法

垃圾回收,有以下几种算法,这里对这几种算法进行分别的阐述。

引用计数法

算法分析

引用计数法是垃圾收集中的早期方式,这种方法中,堆中的每个对象实例都会有一个引用计数,当一个对象被创建的时候,这种对象实例会被分配一个变量,该变量计数设置为1,单任何变量被赋值为这个对象引用的时候,计数将会加1,但是超过生命周期的时候,将会减少1,任何计数器为0的时候将会做垃圾回收,当一个实例进行垃圾回收的时候,其引用的任何对象实例引用将会减少1.

优缺点

优点:
引用计数器可以很快的执行,但是在程序运行中,对程序需要不被长时间打断的实时环境比较友好。
缺点:
无法检测出循环引用,如果父对象有一个对子对象的引用,子对象反过来也会引用父对象,这样关系永远不可能为0.

tracing算法

根搜索算法

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图
根搜索算法中,程序吧所有引用关系用一张图表示,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕以后,剩余的节点被认为是没有引用的节点。

算法示意图

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图1

算法分析

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

compacting算法

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图2
  标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。

copying算法

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图3
该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

generation算法

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图4
分代垃圾回收算法,分为几代。

年轻代

  1. 所有新生成对象首先会放入年轻代中,年轻代的目标就是尽可能的快速手机哪些生命周期短的对象。
  2. 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
  3. 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收
  4. 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

年老代

1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

  2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
### 持久代
用于保存静态文件,例如Java类,方法等等。

GC

新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

垃圾回收 | Java垃圾回收,这杯咖啡,不仅好喝,而且实用!插图5

Serial收集器(复制算法)

  新生代单线程收集器,标记和清理都是单线程,优点是简单高效。

Serial Old收集器(标记-整理算法)

  老年代单线程收集器,Serial收集器的老年代版本。

ParNew收集器(停止-复制算法) 

  新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

Parallel Scavenge收集器(停止-复制算法)

  并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。

Parallel Old收集器(停止-复制算法)

  Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先

CMS(Concurrent Mark Sweep)收集器(标记-清理算法)

  高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择

GC的执行机制

Scavenge GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。

Full GC

对整个堆进行整理

Java有了GC同样会出现内存泄露问题

1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。
2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

关于作者

我是小小,双鱼座的程序猿,我们下期再见~bye