JVM-垃圾收集算法

Java并不是最早使用自动内存管理的,早在Java之前的Lisp就已经开始使用了,该语言是1958年诞生于麻省理工的一门古老的高级编程语言。当时Lisp语言的设计者就想过一下问题:

  1. 什么样的对象需要回收
  2. 怎样回收?
  3. 何时回收?

什么样的对象需要回收?

这个问题解释起来很简单相信任何一个人都可以回答:不再使用的对象都可以进行回收。可是应该如何定位这些对象呢?

  1. 引用计数算法
  2. 可达性分析算法

引用计数算法实现简单,就是在对象上都设计一个计数器,每当有一个地方引用,就在该计数器上加一,反之就减一,当对象上的计数器值为零的时候那么就代表该对象不再使用,即可以回收了。这种方式实现简单,效率也相当不错,但是有一个致命问题:无法回收循环依赖的对象。

 如果没有任何地方引用A、B、C这三个对象时,但是这三个对象相互之间引用那么他们的计数器的值最小也会是1,那么这样的对象就没有办法进行回收。当然也不是完全没有办法,只是解决起来就比较费事了。Python使用的就是引用计数算法进行垃圾回收的。

至于可达性分析算法,就是预先定义系列GC Roots,如果对象到GC Roots之间不可达,那么就证明该对象不在使用可以进行回收了。他的先天优势就是可以处理循环依赖,但是实现起来就比较复杂了。

 如图所示,对象A、B、C之间相互引用,但是到GC Roots是不可达的那么这三个对象就可以被回收了。GC Roots都有什么呢?

  1. 虚拟机栈中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中引用的常量对象
  4. 在本地方法栈中JNI引用的对象
  5. Java虚拟机的内部引用,例如基本数据类型对应的Class对象
  6. 所有被synchronized持有的对象
  7. 反映了Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

怎样回收

这里就要谈一下垃圾收集算法了:

  1. 标记-清除算法:该算法是最基础、最简单也是速度最快的垃圾收集算法,但是它会造成内存碎片化问题,以至于后边创建新的对象时有可能无法找到足够大的连续的内存空间而提前GC
  2. 标记-复制算法:该算法的宗旨是将一块内存区域等分为两块大小相同的内存区域,每次只使用其中的一块,每当GC的时候就把当前使用的一半内存中存活的对象复制到另一半没有使用的内存区域中,这样做就解决了内存碎片化的问题,但是也同时浪费了一半的内存空间。
  3. 标记-整理算法:也叫标记压缩算法,它的原理就是将所有存活的对象向内存区域的一端移动,清理剩下的内存空间。他与标记-清除算法的本质区别的就是后者是非移动式的而它是移动式的。他们两个各有优点,标记-清除算法速度快但是会造成内存碎片化问题也就是分配内存时难,标记-整理算法不会产生内存碎片化问题,速度慢,该算法经常用到老年代中。

每一款垃圾收集器都使用了不同的垃圾收集算法。现在知道了怎么区收集不用的对象,那么如何定位呢?前边提到过定位问题一般有两种解决方案,引用计数算法和可达性分析算法,而在Java中使用的便是可达性分析算法。从垃圾收集算法中可以看到,所有算法都有一个标记的字眼,其实不难理解收集收集齐整体的收集思路就是先标记出那些对象是要被清除的,然后再进行统一清理。其中的标记过程就是要提到关于可达性分析算法的根节点枚举了

根节点枚举

根节点枚举就是从GC Roots集合开始向下遍历,标记出存活的对象(也可以标记出死亡的对象)。但问题是随着Java应用的越发壮大,一个方法区都有可能有上几百兆的,而且有一个致命的问题就是根节点枚举是STW(Stop The World: 停止所有用户线程)的,那就有可能造成相当长的一段停顿时间。这可不是一个好主意,试想一下你在用着程序好好的突然来了几十秒的,甚至几分钟的服务未响应是个什么感觉?好在JVM有自己的解决办法:根节点枚举并不需要枚举所有的GC Roots,而是在类加载完成的时候,Hotspot会把对象内什么位置上是什么类型的数据计算出来(即便是即时编译也会有响应的记录),存放到一组叫做OopMap的数据结构上。这样收集器扫描的时候就能直接得知这些信息了。

安全点

前边说根节点枚举的时候提到过STW,这是需要停止用户线程的。那么什么时候停止?这就要谈一下安全点了。

前边提到过使用OopMap可以根节点枚举的时候不去扫描所有的GC Roots,那么这个OopMap是存放到哪里呢?他是被设计在指令上的。但是也没有必要为每一个指令都定义一个OopMap,而HotSpot实现上也是在特定的位置记录这些信息,这些位置被称为安全点。有了安全点那么就是说用户线程不能在任意位置上停止,只能到达安全点的时候才能够停止。那么如何保证所有线程在发生GC时都跑到安全点上呢?有两种方案:

  1. 抢占式中断:在发生GC虚拟机先将所有用户线程停止,然后没有到达安全点的线程继续运行到最近的安全点上然后再次停止
  2. 主动式中断:发生GC的时候设置一个标志位,所有线程在运行的时候不停的轮询这个标志位如果为真则到最近的安全点上挂起线程

现在几乎没有虚拟机选用抢占式中断了,几乎所有的虚拟机都是采用主动式中断。

常见的安全点如方法调用、循环跳转、异常跳转等一系列“长时间”执行的指令

安全区域

安全点几乎可以解决用户线程如何停顿了,但是如果有一个线程在发生GC的时候不执行,如调用了sleep?这个时候就需要安全区域的概念了,安全区域就是指能够保证在某一段代码片段之中,引用关系不会发生变化,因此在这个区域没任意地方开始垃圾收集都是安全的。当线程进入安全区域的时候首先要标识自己已经进入了安全区域,这段时间内发生垃圾收集就不必去管他们了。当线程要离开安全区域的时候,他们要检查虚拟机是否完成了根节点枚举,如果完成了继续执行,否则直到收到可以离开安全区域的信号为止。

何时回收

这个问题很好回答,当创建新对象的时候没有连续的内存空间容纳新对象就需要进行垃圾回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值