好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析

0x01 什么是垃圾很简单 , 没有引用指向的任何对象都叫做垃圾(garbage) 。
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
什么是garbage
在某一内存空间中 , Java程序制造了很多对象被引用 , 有的对象还引用别的对象 , 中途有对象不被需要了就没有指向他的引用了 , 这些没有引用指向的东西就是垃圾 。
这些垃圾不需要自己回收 , JVM中有类似于街道上那些勤劳的环卫工的人 , 在帮忙回收垃圾 。
0x02 如何找到垃圾那么 , 帮忙回收垃圾的人是如何找到垃圾的呢?JVM中一般有两种算法:

  • Reference Count 引用计数
  • Root Searching 根可达算法
2.1 引用计数引用计数法设定给对象中添加一个引用计数器 , 每当有一个地方引用它时 , 计数器值加1;当引用失效时 , 计数器值减1 , 引用数量为0的时候 , 则说明对象没有被任何引用指向, , 可以认定是“垃圾”对象 。
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
引用计数法
但是引用计数法不能解决循环引用的问题 , 比如O1->O2->O3->O1 , 当没有引用指向他们任何一个的时候 , 他们的reference count都是1 , 按照引用计数法 , 他们都不是垃圾 。
而事实上 , 没有任何引用指向O1、O2、O3这一坨了 , 他们都是垃圾 。
JVM(看向O1):坚决不能容忍垃圾!
O1:看什么 , 我的引用计数为1 , 不是0 , 我不是垃圾 。
JVM:不好意思 , 我不是针对你 , 我是说你们一坨都是垃圾!
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
引用计数法无法确定垃圾的情况
2.2 根可达算法引用计数法不能解决循环引用的问题 , 可采用根可达算法(Root Searching) 。
其算法思路就是通过一系列名为 GC Roots 的对象作为根 , 从根上开始向下搜索 , 搜索所走过的路径称为引用链(Reference Chain) , 当一个对象到 GC Roots 没有任何引用链相连时 , 则证明此对象是不可用的 。
那么哪些是 Roots 呢?
  • 线程栈变量
Java程序从main方法开始执行 , main方法会开启一个线程 , 这个线程里有线程栈 , 里面有栈帧 。
从main开始这个线程栈帧里面的这些个叫做根对象 。
  • 静态变量
一个class被load到内存之后 , 马上就对静态变量进行初始化 , 所以静态变量访问到的对象也是根对象 。
  • 常量池
如果一个class能够用到其他的class的对象 , 那么他就是根对象 。
  • JNI指针
本地方法用到本地的对象也是根对象 。
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
总之 , 当一个程序起来之后马上需要的对象叫做根对象 。
0x03 常见的垃圾回收算法垃圾找到了之后就要回收 , 那么JVM怎么进行垃圾回收呢?
【好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析】如何进行垃圾清除 , 常用的算法有3种:
  • Mark-Sweep 标记清除
  • Coping 拷贝算法
  • Mark-Compact 标记压缩
3.1 标记清除先标记垃圾 , 然后清除垃圾 。 通过该算法 , 先找到那些有用的对象 , 没有用的并出来然后把它们清除掉 。
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
从图中可以看出 , 标记清除算法将垃圾标记并清除后 , 内存中原先不可用的内存变成了空闲的可用的 , 但是这些内存有些有些不连续了 , 也就是说产生了碎片 。
再一个 , 如果存活的对象比较多 , 这种情况标记清除算法的执行效率比较高 。 相反执行效率就稍微低一点 , 因为需要两遍扫描 , 第一次扫描找到那些有用的 , 第二次扫描把那些没用的找出来清理掉 。
3.2 拷贝算法拷贝算法 , 就是把内存一分为二 , 比如A、B两个区域 , 分开之后 , 把A区域中有用的拷贝到B区域 , 拷贝完成后 , 把A区域全部清除 , 下次再分配内存的时候先往B区域分配 , 如此往复 。
1) 把内存一分为二 , 分为A、B两个区域 , 分配内存先往A区域分配
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
2) 拷贝A区域中存活的有用的对象到B区域
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图
拷贝完成的状态:
好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析文章插图