您的位置:首页 > 编程语言 > Java开发

GC和垃圾回收器其一

2019-07-22 12:03 666 查看

什么是GC

GC(Garbage Collection)垃圾回收,释放垃圾占用的空间,对堆中已经死亡或者长时间没有使用的对象进行清除和回收,防止内存泄漏。可以有效使用内存空间。

什么是垃圾

垃圾收集之前需要定义什么是垃圾,之后才能决定如何回收垃圾。

抛开书面上介绍的几种垃圾分析算法,一步到位说下jvm采用的可达性分析法

可达性分析

基本思路是通过根引用(GC ROOT)作为分析起点,沿着节点向下搜索,搜索路径称为引用链(Reference Chain),当一个对象到GC ROOT没有任何引用链时,证明对象不再使用(GC ROOT到对象节点不可达)。

通过可达性分析算法可以解决引用计数无法解决的“循环依赖”,只要对象和GC ROOT之间无法建立直接或者间接的链接,就可以认定为可回收对象。

GC ROOT

既然GC ROOT在对象可达性分析时如此重要,那么哪些对象可以作为GC ROOT呢?

在之前需要先了解下JVM的内部结构:

以下四种情况可以认定为GC ROOT:

  • 虚拟机栈(栈帧的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

垃圾回收

在确定垃圾之后就可以进行垃圾回收了,回收过程中最重要的一点就是如何高效的回收,这些也都是不同版本回收器进化的主要目标。

常见的垃圾收集算法:

  • 标记清除
  • 复制算法
  • 标记整理

标记清除

标记清除(Mark-Sweep)是将内存中认定为垃圾的对象进行标记,之后对这些标记的对象进行清理,清理之后的空间可以给新对象使用。

但是这种操作算法存在的问题会存在大量不连续的清理空间,也就是内存碎片。内存碎片带来的问题是,有的时候我们进行对象分配时,需要连续的内存(比如数组这种)但是由于内存中没有足够的联系内存,导致碎片内存用不了,造成了内存空间的浪费。

复制算法

复制(Copying)是在标记清除之上演进而来的,主要目的是解决内存碎片问题,将可用内存空间按照容量进行划分成大小相等的两块,每次只是用其中一块,当这部分内存使用完成之后,可以把存活对象复制到另一块上面,把之前使用的内存进行一次性清理,保证内存空间连续可用,复制算法我们可以不考虑内存碎片等问题,分配过程更简单高效。

但是复制算法带来的就是空间的代价更大。

标记整理

既然标记清除,复制算法都存在一些明确的短板,是否可以针对这些短板设计一个回收算法呢?于是就有了标记整理算法(Mark-Compact)。

标记整理过程和标记清除算法一样,但是后续整理方式是将存活对象统一移动到内存到一侧,在进行边界外的对象清除。

这种算法属于标记清除算法的升级版本,可以解决内存碎片问题,也避免了复制算法存在一半空间浪费的问题,但是也不是没有问题。首先他会造成更多的内存变动,比如需要判断存活对象,整理存活对象地址,效率上对于复制算法来说要差很多。

分代回收

我们了解了三种收集算法,JVM内存回收就可以根据自己特点进行算法选择了。

比如JVM根据对象存活周期不同将内存划分成不同的几部分。比如堆中针对于快生快消特点分出了新生代,存活较久对象分出了老年代。于是针对不同代对象特点可以选择不同的回收算法,比如年轻代对象生命周期比较短,可选择复制算法减少标记整理的代价。老年代存活对象较多,空间利用率上相对要求较高,需要使用标记清理或者标识整理算法。

JVM内存模型

那么JVM中对象具体是怎么分配的呢?从内存模型讲起。

堆是内存中最大的一块,也是垃圾回收的主要战场。

堆主要分为两个区域:年轻代和老年代,年轻代又分为Eden,s1,s2区。

Eden

新生对象优先在Eden中分配,当Eden中没有足够空间后,会触发一次YGC(比较讨厌用Minor GC和Major GC的说法),YGC之后Eden区被回收,无需回收的对象进入S1区,如果S1区容不下则直接进入老年代。

S区

S区主要作用是作为Eden和Old之间的缓冲带,因为之前说过Eden大部分对象都是短生命的,所以为了避免YGC之后直接进入老年代,引入S区也是有必要的,缓冲了因为频繁YGC使得老年代被填满的风险。

因为一而再再而三之后大部分对象还是在新生代消亡了,所以设置一个S区是明智的。

一般对象需要经过15(默认)YGC之后才进入老年代。

而两个S区的目的就是为了采用复制算法,来解决碎片问题。如果一个区域的话你就只能采取标记整理或标记清除算法了,为了降低YGC期间(因为YGC太频繁了)对于程序的影响,用复制算法这种简单可依赖的方式还有有必要的。

如果分成好几个S区会有什么问题呢?频繁的复制是个问题,过多的S区空间造成整体空间利用率更低也是个问题,相信这个2的阈值也是经过多次实验得来的,所以GC的很多默认算法其实是很优的配置,除非结合自己业务特点,否则比建议进行修改。

Old区

默认老年代占用整个区的2/3,只有发生Full GC(这个说法也不准确,我们用CMS回收器,所以就称为CMS GC吧)时才进行整理,Full GC会造成STW,内存越大STW肯定时间就越长(这个也是我们调优JVM参数一个很重要的参考点),所以内存并不是越大越好。上面说了老年代存在大量长期对象,所以采用标记算法更合适。

哪些对象会进入老年代?

  • 内存担保,无法放置对象直接进入老年代
  • 大对象直接进入老年代,可配置
  • 长期存活对象进入老年代,比如age=15
  • 动态对象年龄进入老年代,主要是s区空间不足了,某一个年龄及以上对象大小总和大于整个S区一半,这些年龄的对象直接进入老年代
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java Sweep Mark