新生代和老年的垃圾回收算法介绍

垃圾回收算法

上一篇文章我们介绍了堆内存分配、jvm分代模型、垃圾回收触发条件以及jvm的一些核心参数设置,理解了这些内容之后,我们从本文开始介绍jvm的垃圾回收算法。

复制算法

针对新生代的垃圾回收算法叫做\color{green}{复制算法}。简单来说,首先把新生代的内存分为两块。当有对象被创建就会分配在其中一块内存空间里,对象被方法局部变量来引用。随着系统的运行,大量的对象被创建并分配在新生代的其中一块内存区域里,而且分配过后,很快就失去了局部变量或者类静态变量的引用,成为了垃圾对象。当新生代那块被分配对象的内存区域基本快满了,再次要分配对象的时候发现内存空间不足了,那么此时就会触发Minor GC回收掉新生代那块被使用的内存空间的垃圾对象。
\color{green}{一种不太好的垃圾回收思路}
假设现在采用的垃圾回收思路就是直接对被使用的那块内存区域中的垃圾对象进行标记,找出可以被垃圾回收的对象,然后直接对那块内存区域中的对象进行垃圾回收,把内存空出来。
\color{blue}{我们思考一下,这种思路好吗}
用这种思路去做垃圾回收,可能回收完毕之后\color{green}{造成大量的内存碎片}。那么什么是内存碎片呢?我们下面来看一张图,这样会更加直观,示例图如下:

neicun.jpg

上面用红圈标记出来的区域就是所谓的内存碎片。在各种凌乱的存活对象的中间,出现了大量的红圈圈出来的内存碎片。这些内存碎片大小不一样,有的可能很大,有的可能很小。
那么内存碎片太多会造成什么问题呢?
\color{red}{内存浪费!}

啥意思?比如现在打算分配一个新的对象,尝试在上图那块被使用的内存区域里分配,虽然所有的内存碎片加起来其实有很大的一块内存,但是因为这些内存都是碎片式分散的,可能会因为碎片太多的缘故,导致没有一块完整的足够的内存空间来分配新的对象。
所以这种直接对一块内存空间回收掉垃圾对象,保留存活对象的方法绝对是不可取的。因为内存碎片太多就是它最大的问题。
\color{green}{一个合理的垃圾回收思路}

那么有没有一种合理的思路来进行垃圾回收呢?
\color{red}{当然有!}
这个时候上图中一直没有派上用场的另外一块空白的内存区域就出场了。首先,并不是按照上述思路直接对已经使用的那块内存把垃圾对象全部回收掉,然后保留全部存活对象。而是先对那块在使用的内存空间标记出里面哪些对象是不能进行垃圾回收的,也就是要存活的对象,然后先把那些存活的对象转移到另外一块空白的内存中。如下图:
xinshengdai.jpg

这样就可以让被转移的那块内存区域没有什么内存碎片,对象都是按顺序排列在这块内存里的。这时候再一次性把原来使用的那块内存区域中的垃圾对象全部一扫而空,空出来一块内存区域。这就是所谓的
\color{green}{复制算法}
,把新生代内存划分为两块内存区域,然后只使用其中一块内存,等那块内存快满的时候,就把里面存活的对象一次性转移到另外一块内存区域,保证没有内存碎片。接着再一次性回收原来那块内存区域的垃圾对象,再次空出来一块内存区域。两块内存区域就这么重复着循环使用。
\color{red}{复制算法有什么缺点?}

复制算法的缺点其实非常明显,如果按照上述思路,大家会发现,假设我们给新生代1G的内存空间,那么只有512MB的内存空间是可以用的,另外512MB的内存空间是一直要空着的,从始至终,就只有一半的内存可以使用,这样的算法显然对内存的使用效率太低了。
\color{green}{复制算法的优化:Eden去和Survivor区}

我们之前分析过,绝大多数的对象都是存活周期非常短的对象,可能被创建出来1毫秒之后就没人引用了,它就是垃圾对象了,所以大家可以想象一下,可能一次新生代垃圾回收过后,99%的对象其实都被垃圾回收了,就1%的对象存活下来,可能就是一些长期存活的对象,或者还没有使用完的对象。所以实际上真正的复制算法会做出如下优化,把新生代内存区域划分为三块:
eden.jpg

1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存。平时可以使用的就是Eden区和其中一块Survivor区,那么相当于就是有900MB的内存是可以使用的。
但是刚开始的时候对象都是分配在Eden区内的,如果Eden区快满了,此时就会触发垃圾回收,就会把Eden区中的存活对象都一次性转移到一块空着的Survivor区。接着Eden区就会被清空,然后再次分配新对象到Eden区,然后Eden区和一块Survivor区里是有对象的,其中Survivor区里放的是上一次Minor GC后存活的对象。
如果Eden区再次被放满,那么再次触发Minor GC就会把Eden区和放着上一次Minor GC后存活对象的Survivor区内的存活对象,转移到另外一块Survivor区里,然后再一次性把Eden区和之前使用的Survivor区里的垃圾对象全部回收掉。

对象什么时候从年轻代进入老年代

\color{red}{新生代里的对象一般在什么情况下会进入老年代?}\color{green}{答案是躲过15次GC之后进入老年代。}其实大家可以理解为我们写的系统刚启动的时候,创建的各种各样的对象,都是分配在新生代里的,然后随着系统的运行,新生代就满了,此时就会触发Minor GC,可能就1%的少量存活对象转移到空着的Survivor区中。随着系统继续运行,不断重复这个过程。
那么之前给大家讲过,我们写的系统中有些对象是长期存在的,它是不会轻易的被回收掉的,比如下面这段代码:

public class User {
      private static Role role = new Role();
}

只要“User”这个类还存在,那么它的静态变量“role”就会长期引用“Role”实例对象,所以你无论新生代怎么垃圾回收,类似这种对象都不会被回收掉。这类对象每次在新生代里躲过一次GC被转移到一块Survivor区域中后,它的年龄就会增长一岁,默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,它就会转移到老年代里去。这个具体是多少岁进入老年代,可以通过jvm参数“-XX:MaxTenuringThreshold”来设置,默认是15岁。

\color{green}{动态对象年龄判断}
根据对象年龄,有另外一个规则可以让对象进入老年代,不用等15次GC过后才可以。大致的规则是,假如说当前放对象的Survivor区域里一批对象的总大小大于这个Survivor区域的内存大小的50%,那么大于等于这批对象年龄的对象就可以直接进入老年代了。举个栗子:假设Survivor区大小100MB,有两个对象,这俩对象的年龄一样,都是2岁,然后两个对象加起来超过了50MB,超过了Survivor区的内存大小的一半了,这个时候,Survivor区里的大于等于2岁的对象就要全部进入老年代。这就是\color{green}{动态对象年龄判断}的规则,这条规则也会让一些新生代的对象进入老年代。
实际上这个规则运行的时候是如下逻辑:年龄1+年龄2+年龄N的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄N以上的对象都放入老年代。
其实说白了,无论是15岁的那个规则,还是动态年龄判断的规则,都是希望那些可能是长期存活的对象尽早进入老年代。

\color{red}{大对象直接进入老年代}
有一个jvm参数叫“-XX:PretenureSizeThreshold”,可以把它的值设置为字节数,比如“1048576”字节,就是1MB。它的意思是如果你要创建一个大于这个大小的对象,比如一个超大的数组,此时就直接把这个大对象放到老年代,压根就不会经过新生代。之所以这么做,就是要避免新生代里出现那种大对象,屡次躲过GC,还得把他在两个Survivor区里来回复制多次之后才能进入老年代,那么大的一个对象在内存里来回复制,是相当耗费时间的。所以说这也是一个对象进入老年代的规则。

\color{red}{Minor GC后的对象太多无法放入Survivor区怎么办?}
现在有一个比较大的问题,就是如果在Minor GC之后发现剩余的存活对象太多了,无法放入另外一块Survivor区怎么办?假设Survivor区有100MB内存,发生GC的时候发现Eden区里有超过150MB的存活对象,没办法放入Survivor区里,此时怎么办?\color{green}{这个时候就必须得把这些对象直接转移到老年代。}

老年代空间分配担保规则。
这个时候大家又想提另外一个问题,如果老年代里的空间也不够放这些对象呢?别急,我们来一步一步分析。首先,在执行任何一次Minor GC之前,jvm会先检查一下老年代可用的内存空间是否大于新生代所有对象的总大小。为什么检查这个呢?因为最极端的情况下,可能新生代Minor GC过后所有对象都存活了下来,那岂不是新生代所有对象全部要进入老年代?
如果说发现老年代的可用内存大小是大于新生代所有对象的,此时就可以放心大胆的对新生代发起一次Minor GC了,因为即使Minor GC之后所有对象都存活了,Survivor区放不下了,也可以转移到老年代里去。\color{red}{但是,}假设执行Minor GC之前发现老年代的可用内存已经小于了新生代的全部对象大小了,那么这个时候是不是有可能发生在Minor GC之后新生代的对象全部存活下来,然后全部需要转移到老年代,但是老年代空间又不够的情况?理论上是有这种可能的!

这个时候jvm就会看一个叫“-XX:-HandlePromotionFailure”的参数是否设置了,如果有这个参数,那么就会继续进行下一步判断,就是看老年代的内存大小是否大于之前每一次Minor GC后进入老年代的对象的平均大小。

举个栗子,之前每次Minor GC后,平均有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB,这就说明,很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的。

如果上面那个步骤判断失败了,或者是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接出发一次 “Full GC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。如果上面两个步骤都成功了,那么就是说可以冒点风险尝试一下Minor GC。此时进行Minor GC有几种可能。

第一种可能,Minor GC过后,剩余的存活对象的大小,小于Survivor区的内存空间,那么此时存活对象进入Survivor区域即可。
第二种可能,Minor GC过后,剩余的存活对象的大小,大于Survivor区域的内存空间,但是小于老年代可用内存空间,此时直接进入老年代即可。
第三种可能,Minor GC过后,剩余的存活对象的大小,大于Survivor区域的内存空间,也大于老年代可用内存空间,此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触发一次“Full GC”。

Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。因为这个时候必须把老年代里没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里。如果Full GC过后老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的“OOM”内存溢出了。

适合老年代垃圾回收的算法——标记整理算法

简单来说,老年代采取的是\color{green}{标记整理算法}。大家看下图:

lnd.jpg

首先,标记出来老年代当前存活的对象,这些对象可能是东一个西一个的。接着,会让这些存活对象在内存里进行移动,把存活对象尽量都挪动到一边去,让存活对象紧凑的靠在一起,避免垃圾回收后出现过多的内存碎片。然后再一次性把垃圾对象都回收掉。这里要注意,老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354