每天学习一点JVM之:垃圾回收机制

1,741

关于JVM系列的文章,都是在读了《深入理解java虚拟机》一书之后的读书笔记总结。

每个人入门java的时候,基本上都会听到的关于java的一个邮电就是java的内存管理功能。使用java的时候不需要将过多的心思摆在内存管理的问题上(实际上,内存管理是开发者始终关注的话题,尤其是移动app的开发,万一心情不好就oom了呢)。今天简单总结一下java的垃圾回收的知识点。

JAVA的引用

java中引用包括下面四种:

  • 强引用

    程序中普遍存在的类似“Object object=new Object()”这种类型的引用属于强引用。垃圾回收器永远不会回收被强引用所引用的对象。

  • 软引用

    用以描述有用但却并非必需的对象。对于软引用所引用的对象,在系统将要发生oom异常之前,将会对这些对象列进回收范围之中进行第二次回收。

  • 弱引用

    用于描述非必需的对象,强度较软引用弱。软引用关联的对象只能生存到下一次垃圾收集发生之前。无论当前内存是否足够,它都会在垃圾收集器开始工作的时候被回收。

  • 虚引用

    也称为幽灵引用或者幻引用。虚引用的存在不会对对象的生存时间产生影响,也无法通过虚引用取得对象的实例。为对象设置虚引用的唯一目的就是能在这个对象呗垃圾收集器回收时收到一个系统通知。

对象存活的判定

  • 引用计数算法

    给对象添加一个引用计数器,当有地方引用该对象时,计数器加一;引用失效时,计数器减一。任意时候,只要计数器不为零,就表示该对象尚存在引用关系,否则表示对象不能再被使用。

  • 可达性分析算法

    以一系列可以被称为gc-roots的对象为起点并向下搜索,搜索走过的路径为引用链,当对象和gc-roots之间没有任何的引用链的话,则该对象是不可用的,如下图所示:

java中可以被看作是gc roots的对象包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • native方法引用的对象

gc roots不可达的对象,在被回收之前至少要经历两次被标记的过程,才能确定是否会被垃圾回收器回收。在对象被发现没有引用链的时候,会被第一次标记并且进行一次筛选(筛选的条件是该对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者该方法已经被虚拟机调用过的话,虚拟机都会认为没有必要执行finalize()方法)。如果对象没能在finalize()方法中重新于引用链上的任意对象建立关联关系,那么对象将会在第二次标记的时候被回收。

垃圾收集算法

  • 标记-清除算法

算法分为标记和清除两个阶段。首先标记出所有需要回收的对象,在标记完成后统一回收被标记的对象。(这个方法效率较低,而且回收之后会产生大量的不连续的内存碎片)示意图如下所示:

  • 复制算法

将内存按容量划分为等大的两块,每次只使用其中的一块内存,当这一块内存将用完的时候就将还存活着的对象复制到另一块内存上面,然后再把已使用过的内存空间一次清理掉。示意图如下所示:

这个算法的一种改进做法是在在回收新生代的时候,将内存分为一块较大的eden空间和两块较小的survivor空间,每次使用eden和其中的一块survivor空间。当回收的时候,将其中还存活的对象一次性地复制到另外一块survivor空间上,最后清理掉eden和之前使用的survivor空间。

  • 标记-整理算法

标记过程于标记-清除算法一样,但是并不直接对可回收对象进行清理,而让所有存活的对象都往一端移动,然后清理端边界以外的内存(针对老年代存活率比较高的现象而提出).示意图以下所示:

  • 分代收集算法

它根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。

简单整理如上,如果需要更加深入的学习的话,建议大家可以看一下《深入理解JVM》这本书,个人觉得写得确实很棒。接下来,还会继续写一些这本书的读书笔记。如果大家喜欢,可以点赞收藏。有什么问题欢迎评论一起探讨。大家也可以关注我,我会在工作之余分享自己学习android的一些东西。最后,感谢你宝贵的时间阅读这篇文章,谢谢!