JVM之旅--GC探秘

关于本文的说明,在jdk8版本中,heap中的持久带已经被甲骨文抛弃了,类的元信息存储也移动到native heap中,增长了一些新的tag(具体的甲骨文文档有说),本文针对的heap结构还保留持久带,不针对最新jdk,本文主要是内存分配以及GC工做原理的分析,咱们学习jvm,一方面在享受java关怀的同时也理解下jvm背着咱们开发者干了什么,另外一方面也感悟一下这种运行内核设计的思想,甚至能够说艺术。因此,我我的以为版本的差别对本文分析的影响不大。你们注意下jdk8heap结构的改动便可。java

说到GC,首先要说到GC实现一个很重要的角色,那就是jvm(java虚拟机),jvm做为java的一个托管平台在java代码的运行过程当中提供了诸多的性能优化,确保代码的高效率执行,至于怎么优化就暂且不关注这个问题,咱们先来看看oracle对于jvm的架构说明:android

这里写图片描述

在这个图里面咱们能够看到,咱们用java写的各类类都是经过一个类加载器交换到jvm,jvm的中间层包括java方法存储区,以及java中堆(heap),在java代码中new分配内存的对象都会被托管在heap中,再通过jvm底层的jit编译器以及执行引擎的优化后变成(mapping)到内存空间,中间层还包括java线程跟一些本地线程等资源的运行空间,最后看到咱们的gc是放在jvm的最底层。c++

说说gc的本质究竟是什么吧,其实就是一个watcher进程,开发过Android的能够在debug模式下看到一个gc线程,gc监视的目标就是heap中对象,在jvm中对于heap有一个区域划分模型,咱们先来看看(非jdk8版本结构):web

这里写图片描述

刚被new出来的对象都会存在于heap中Eden区,但这个区域是有本身的容量的,满了gc就会开始工做了,gc主要是检查哪些对象没有被引用了(沿着引用链寻找,实际上是引用链图的广度优先遍历),或者判断之后也不会被引用,这些废物就会被gc标记,成为marking过程,以下图:性能优化

这里写图片描述

marking以后(在上图绿色的表明还被引用着的对象,粉红色表明没有被引用,以及推断之后也没有引用可能性的对象,变为粉红色表明已经被标记),以后gc开始进入sweep/compact阶段,先deletion在进行内存整合,这里就把两个合起来吧,而后Eden区的对象就变成这样:架构

这里写图片描述

在mark-sweep方式下,多出来的内存空间会以引用的形式添加到jvm内部的一个内存链表中,下次new一个对象须要内存的分配时取出一个大小适合的内存空间的引用(链表的形式容许内存空间的不连续分布)。oracle

固然这种方式可能会致使内存碎片。碎片化是比较可怕的,全是1k的离散碎片我分配2k它都无法玩了,因此也就存在另外一种compact方式,会进行内存的整合(结果就如上图),但这意味着内存的移位,对于给jvm划分较大的heap状况下这样也够呛,因此两种方式的使用仍是看状况的。app

在Eden区的垃圾回收成为Minor garbage collections这个名字,这个回收在jvm中被优化得很厉害,基本算上是gc最快速度的垃圾回收了,并且这个举动还特别的伟大,得整个jvm上下都得为她让道,叫作Stop the World Event,观察过android dalvik的log输出的伙伴可能想起来常常会看到gc的垃圾回收提示,free了多少多少k的内存,其实那个时候全世界只有gc在工做,因此她的效率不高那就是一个很欠揍的行为了。jvm

好咯,Eden区一旦满了gc就会被触发,这个时候没有被引用的对象就像上面描述的过程同样被标记而后再被销毁咯,被引用的对象这时候可谓幸存者了,因此它们被移到幸存者区,即s0区,这个时候的Eden区是空的,在s0,对象的生命计数仍是在进行的,通过一轮major gc的生死历练就得进入old generation区了.svg

然而jvm有个有趣的地方就在于s0与s1两个区是交替使用的,在下一轮gc被触发时,引用对象会被挪到s1,s0的对象也都被所有转移到s1,下一轮再转移到s0,就这么来回折腾,反正每次都保证Eden区跟其中一个s区都是空的,另一个s区存在着不一样年龄的对象(在对象的生命周期中,就这么叫吧),当到达必定阙值时,对象就会转移到下一个generation区,长大了嘛,确定须要更多的发展空间,这个区的gc运行速度会比在Eden慢一点。

固然,这个区的垃圾回收也是一个Stop the World events,没了引用就得被清除掉,这个过程叫作major garbage collection,最终来讲,这个区的对象都要被回收的,所以这个过程要缓慢一不少。

至于咱们的用的弱引用,软引用,在内存告罄时也是必须为heap做出贡献的,GC就喜欢欺负弱的引用了,其实GC在mark完了以后还会给标记对象一个续关的机会,不过得检查它是否重写了finalize方法,若是在这个方法中它能及时地将本身添加到引用链图,也就是说本身要让本身在临死前忽然变得有价值,这样GC才能大发慈悲的赦免它。

另外,survivor区若是咱们指定jvm分配的过小,有时候咱们对象分配的太疯狂,它也没法容纳太多的幸存者,因此也会在预测计算老生带空间不够时致使full gc进行空间担保,为无处可去的对象敞开温暖的怀抱。

ok,如今就详细分析了一下gc的工做过程以及对象在heap中的一辈子,本次文章就到这里结束了,关于gc的标记决策,之后会再来一篇文章进行说明,主要是分析如何判断对象有没有被引用,毕竟java又不像c++同样存在智能指针,也比较非主流的不搞引用计数,本文结束。。。