写点什么

JVM 内存管理 --GC 算法精解(五分钟教你终极算法 --- 分代搜集算法)

作者:java易二三
  • 2023-08-08
    湖南
  • 本文字数:2299 字

    阅读完需:约 8 分钟

引言

何为终极算法?

其实就是现在的 JVM 采用的算法,并非真正的终极。说不定若干年以后,还会有新的终极算法,而且几乎是一定会有,因为相信高人们的能力。

那么分代搜集算法是怎么处理 GC 的呢?

对象分类

上一章已经说过,分代搜集算法是针对对象的不同特性,而使用适合的算法,这里面并没有实际上的新算法产生。与其说分代搜集算法是第四个算法,不如说它是对前三个算法的实际应用

首先我们来探讨一下对象的不同特性,接下来和各位来一起给这些对象选择 GC 算法。

内存中的对象按照生命周期的长短大致可以分为三种,以下命名均为个人的命名。

1、夭折对象:朝生夕灭的对象,通俗点讲就是活不了多久就得死的对象。

例子:某一个方法的局域变量、循环内的临时变量等等。

2、老不死对象:这类对象一般活的比较久,岁数很大还不死,但归根结底,老不死对象也几乎早晚要死的,但也只是几乎而已。

例子:缓存对象、数据库连接对象、单例对象(单例模式)等等。

3、不灭对象:此类对象一般一旦出生就几乎不死了,它们几乎会一直永生不灭,记得,只是几乎不灭而已。

例子:String 池中的对象(享元模式)、加载过的类信息等等。

对象对应的内存区域

还记得前面介绍内存管理时,JVM 对内存的划分吗?

我们将上面三种对象对应到内存区域当中,就是夭折对象和老不死对象都在 JAVA 堆,而不灭对象在方法区

之前的一章中我们就已经说过,对于 JAVA 堆,JVM 规范要求必须实现 GC,因而对于夭折对象和老不死对象来说,死几乎是必然的结局,但也只是几乎,还是难免会有一些对象会一直存活到应用结束。然而 JVM 规范对方法区的 GC 并不做要求,所以假设一个 JVM 实现没有对方法区实现 GC,那么不灭对象就是真的不灭对象了。

由于不灭对象的生命周期过长,因此分代搜集算法就是针对的 JAVA 堆而设计的,也就是针对夭折对象和老不死对象

JAVA 堆的对象回收(夭折对象和老不死对象)

有了以上分析,我们来看看分代搜集算法如何处理 JAVA 堆的内存回收的,也就是夭折对象与老不死对象的回收。

夭折对象:这类对象朝生夕灭,存活时间短,还记得复制算法的使用要求吗?那就是对象存活率不能太高,因此夭折对象是最适合使用复制算法的

小疑问:50%内存的浪费怎么办?

答疑:因为夭折对象一般存活率较低,因此可以不使用 50%的内存作为空闲,一般的,使用两块 10%的内存作为空闲和活动区间,而另外 80%的内存,则是用来给新建对象分配内存的。一旦发生 GC,将 10%的活动区间与另外 80%中存活的对象转移到 10%的空闲区间,接下来,将之前 90%的内存全部释放,以此类推。

为了让各位更加清楚的看出来这个 GC 流程,LZ 给出下面图示。



图中标注了三个区域中在各个阶段,各自内存的情况。相信看着图,它的 GC 流程已经不难理解了。

不过有两点 LZ 需要提一下,第一点是使用这样的方式,我们只浪费了 10%的内存,这个是可以接受的,因为我们换来了内存的整齐排列与 GC 速度。第二点是,这个策略的前提是,每次存活的对象占用的内存不能超过这 10%的大小,一旦超过,多出的对象将无法复制

为了解决上面的意外情况,也就是存活对象占用的内存太大时的情况,高手们将 JAVA 堆分成两部分来处理,上述三个区域则是第一部分,称为新生代或者年轻代。而余下的一部分,专门存放老不死对象的则称为年老代

是不是很贴切的名字呢?下面我们看看老不死对象的处理方式。

老不死对象:这一类对象存活率非常高,因为它们大多是从新生代转过来的。就像人一样,活的年月久了,就变成老不死了。

通常情况下,以下两种情况发生的时候,对象会从新生代区域转到年老带区域。

1、在新生代里的每一个对象,都会有一个年龄,当这些对象的年龄到达一定程度时(年龄就是熬过的 GC 次数,每次 GC 如果对象存活下来,则年龄加 1),则会被转到年老代,而这个转入年老代的年龄值,一般在 JVM 中是可以设置的。

2、在新生代存活对象占用的内存超过 10%时,则多余的对象会放入年老代。这种时候,年老代就是新生代的“备用仓库”。

针对老不死对象的特性,显然不再适合使用复制算法,因为它的存活率太高,而且不要忘了,如果年老代再使用复制算法,它可是没有备用仓库的。因此一般针对老不死对象只能采用标记/整理或者标记/清除算法

方法区的对象回收(不灭对象)

以上两种情况已经解决了 GC 的大部分问题,因为 JAVA 堆是 GC 的主要关注对象,而以上也已经包含了分代搜集算法的全部内容,接下来对于不灭对象的回收,已经不属于分代搜集算法的内容。

不灭对象存在于方法区,在我们常用的 hotspot 虚拟机(JDK 默认的 JVM)中,方法区也被亲切的称为永久代,又是一个很贴切的名字不是吗?

其实在很久很久以前,是不存在永久代的。当时永久代与年老代都存放在一起,里面包含了 JAVA 类的实例信息以及类信息。但是后来发现,对于类信息的卸载几乎很少发生,因此便将二者分离开来。幸运的是,这样做确实提高了不少性能。于是永久代便被拆分出来了。

这一部分区域的 GC 与年老代采用相似的方法,由于都没有“备用仓库”,二者都是只能使用标记/清除和标记/整理算法。

回收的时机

JVM 在进行 GC 时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。因此 GC 按照回收的区域又分了两种类型,一种是普通 GC(minor GC),一种是全局 GC(major GC or Full GC),它们所针对的区域如下。

普通 GC(minor GC):只针对新生代区域的 GC。

全局 GC(major GC or Full GC):针对年老代的 GC,偶尔伴随对新生代的 GC 以及对永久代的 GC。

由于年老代与永久代相对来说 GC 效果不好,而且二者的内存使用增长速度也慢,因此一般情况下,需要经过好几次普通 GC,才会触发一次全局 GC。

资料还是要给大家准备的万一需要呢,https://docs.qq.com/doc/DTFdic3lOU3daS3hs《Java 学习、面试;文档、视频资源免费获取》即可免费获取





用户头像

java易二三

关注

还未添加个人签名 2021-11-23 加入

还未添加个人简介

评论

发布
暂无评论
JVM内存管理--GC算法精解(五分钟教你终极算法---分代搜集算法)_编程_java易二三_InfoQ写作社区