Java 中的垃圾回收,经常是由 JVM 帮咱们作好的。虽然这节省了你们不少的学习的成本,提升了项目的执行效率,可是当项目变得愈来愈复杂,用户量愈来愈大时,仍是须要咱们懂得垃圾回收机制,这样也能进行更深一步的优化。 git
垃圾回收( Garbage Collection,如下简称 GC ),从字面上理解,就是将已经分配出去的,但却再也不使用的内存回收回来,以便可以再次分配。github
在 JVM 中,垃圾就是指的死亡对象所占据的堆空间( GC 是发生在堆空间中),那么咱们若是辨别一个对象是否死亡呢?JVM 使用的是引用计数法
和可达性分析
。安全
引用计数法( Reference Counting),是为每一个对象添加一个引用计数器,用来统计引用该对象的个数。一旦某个对象的引用计数器为0,则说明该对象已经死亡,即可以被回收了。多线程
其具体实现为:学习
若是有一个引用,被赋值为某一对象,那么将该对象的引用计数器 +1。优化
若是一个指向某一对象的引用,被赋值为其余值,那么将该对象的引用计数器 -1。线程
也就是说,咱们须要截获全部的引用更新操做,而且相应地增减目标对象的引用计数器。code
看似很简单的实现,其实里面有很多缺陷:cdn
针对第3点,举个例子特别说明一下:对象
假设对象 a 与 b 相互引用,除此以外没有其余引用指向他们。在这种状况下,a 和 b 实际上已经死了。
但因为它们的引用计数器皆不为0(由于相互引用,二者均为1),在引用计数法的计算中,这两个对象还活着。所以,这些循环引用对象所占据的空间将不可回收,从而形成了内存泄露
。
可达性分析( Reachability Analysis ),是目前 JVM 主要采起的断定对象死亡的方法。实质在于将一系列GC Roots
做为初始的存活对象合集(live set),而后从该合集出发,探索全部可以被该集合引用到的对象,并将其加入到该集合中,这个过程咱们也称之为标记(mark)。最终,未被探索到的对象即是死亡的,是能够回收的。
那么什么是GC Roots
呢?咱们能够暂时理解为由堆外指向堆内的引用,通常而言,GC Roots 包括(但不限于)以下几种:
以前咱们说引用计数法
会有循环引用的问题,可达性分析
就不会了。举例来讲,即使对象 a 和 b 相互引用,只要从 GC Roots 出发没法到达 a 或者 b,那么可达性分析便会认为它们已经死亡。
那可达性分析
有没有什么缺点呢?有的,在多线程环境下,其余线程可能会更新已经分析过的对象中的引用,从而形成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。
误报并无什么伤害,JVM 至多损失了部分垃圾回收的机会。漏报则比较麻烦,由于垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则颇有可能会直接致使 JVM 崩溃。
既然可达性分析
在多线程下有缺点,那 JVM 是如何解决的呢?答案即是 Stop-the-world(如下简称JWT
),中止了其余非垃圾回收线程的工做直到完成垃圾回收。这也就形成了垃圾回收所谓的暂停时间(GC pause)。
那 SWT 是如何实现的呢?当 JVM 收到 SWT 请求后,它会等待全部的线程都到达安全点(Safe Point),才容许请求 SWT 的线程进行独占的工做。
那什么又叫安全点呢?安全点是 JVM 能找到一个稳定的执行状态,在这个执行状态下,JVM 的堆栈不会发生变化。
这么一来,垃圾回收器便可以“安全”地执行可达性分析,全部存活的对象也均可以成功被标记,那么以后就能够将死亡的对象进行垃圾回收了。
以上即是发现死亡对象的过程,这也为以后的垃圾回收进行铺垫,具体的垃圾回收过程,我会在下一篇文章中讲述,敬请期待。
有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。