哒哒哒......
回收者的脚步声越来越清晰,我竭力锁紧身体让自己别那么引人注目,尽管气喘吁吁,但我仍然压抑住自己的呼吸。
终归是藏不住的,但是多活个几毫秒也是好的,我们都这么想。
因为回收者是来杀我们的。
第0回 我是一个垃圾
我是一个垃圾,至少我的主人是这么喊我的。
我不知道自己做错了什么,甚至不知道自己做了什么。
我只是被他创造了出来,然后被挪来挪去,我的一生都在漂泊。
听说C帝国的朋友都是他们的主人亲自送他们最后一程,而我的主人,甚至不愿意看我最后一眼,还研究了很多方法,让我被自动回收。
我问他,为什么这么对我?他的回答让我崩溃。
“回收你,与你何干!”
我的眼前一阵眩晕,之前的记忆疯狂涌入我的脑海,“这就是走马灯吗?”心里这么想着,嘴角却挂着笑。好啊,那就顺便回顾一下我这短暂的一生吧,当作我留给世界的一封“遗书”。
第1回 诞生
我诞生在伊甸园(我的主人更喜欢叫它Eden区),名字很不错对吧?充满了原始浪漫的气息。
我并没有见过亚当和夏娃,相反,在这里,我目睹了无数同伴的消逝。
和你们想象的不同,从我们诞生开始,我们在园子里的位置就固定不变了,我们没办法在伊甸园里漫步甚至奔跑。好在我的旁边是个身材娇小的可爱女孩子——小美,我能时不时和她聊天解闷儿,微风吹过,甚至能闻到她身上的香味。
不知道怎么回事儿,接下来一段时间里主人给园子里安置了越来越多的小伙伴,更让我气愤的是,她的身边来了一个巧言令色的臭小子,慢慢地把她的注意力全都吸引过去了!
伊甸园里人越来越多,我却越来越孤单。
我在心里狂喊,“赶紧让这些人都消失吧,只留下我和小美!”
突然间,园内警报声响了起来,原本忙碌的线程都被钉在了原地,一动也不动。
哦,对了,我之前没有跟你们介绍过这些线程。据说他们生来就是为我们服务的,他们一刻不停地访问我们的数据,修改我们的数据,我甚至从来没有见过他们脸上没有汗的样子。
看着他们汗湿的衣服,我甚至觉得他们有点好笑。听人们说他们叫“用户线程”,直到后来知道还有“GC线程”这帮家伙,我才意识到“用户线程”的朴实和可爱。
我们哪里见过这个阵仗呢,一个个地面面相觑,不知道下一步该怎么办。
第2回 幸存者
曾几何时,我发现我来到了一个新的地方,这个地方并不大,远不及伊甸园宽敞。
有一些生面孔,还有一些伊甸园的“老朋友”,我赶紧找小美,终于在我的不远处发现了她。我们的位置依然固定,我和她之间隔了好几个人,所以说话声音不免需要提高一些。
我问她,发生了什么事。
“可能发生了传说中的Minor GC了,听说当园子空间不够了,回收者会回收园子里没用的对象。”
“都是第一次来园子,你咋知道的啊?”我不禁问道。
“是小帅告诉我的。”
又是那家伙!我赶紧找小帅的位置,找了好几次都没有看到他的身影,我瞥了一眼小美伤心的神色,明白了。
小帅是个没用的对象!他被回收者清理了!
是高兴,还是难过,此时对我来说是个问题。没了他,即使小美不喜欢我,我远远地望着也行啊。可是他毕竟是在我的咒骂之后消失的,我心里总有些负罪感。
“那我们现在在什么地方啊?怎么这里还有很多陌生人呢?还有,我们原来那么多的小伙伴,怎么就剩下这么几个了啊?”瞧我这该死的求知欲。
“我们现在处于两个幸存区的其中一个,叫Survivor To,不过现在应该叫做Survivor From了。”小美继续说,“帝国给我们这些新人分配了一块内存区,我们的伊甸园占了其中80%的空间,剩下的就是我们目前所处的幸存区,如你所见,这个地方大概只有伊甸园的八分之一,因为还有另一块同样大小的幸存区。”
“为什么需要两块幸存区呢?”我追问。
“小兄弟,别再问下去了。恐怕那个叫小帅的对象就告诉她这么多了。”我和小美中间的一个对象打断了我说话。
现在想起来,当时小美对他投去了感激的眼神,然而当时的我毫无察觉,转而跟他聊了起来。“那你详细说说呗。”
“刚才那个女孩子所谓的内存区域,叫做新生代。你们这样的新人都是直接被分配到新生代的伊甸园了,伊甸园虽然大,但架不住总有新人来啊,一来二去,就再也容不下新的对象了,这种情况下就会触发Minor GC,将园子里存活的对象连同Survivor From中的对象(如果有的话)一同复制到Survivor To中,然后把Survivor From和Survivor To调换位置,等待下一次Minor GC。”
“我的大多数伙伴都在Minor GC中死去了,我们大部分都是朝生夕死的,对吗?”我有点感伤。
他点点头,“你能活过一轮GC已经很幸运了,你看看你头顶的标记。”
我用手摸了摸额头,发现原本的0000已经变成了0001。
“每经过一轮Minor GC,我们的年龄就增长1岁,直到变成1111,也就是经历15次GC,我们就可以进入传说中的老年代了。”
第3回 帝国的走狗
之后,他又给我讲了很多,尤其是每次面对回收者Serial的故事。
Serial是Minor GC的掌管者,我问他,除了Minor GC还有其他的GC吗?他说他不知道,因为他从来没有去过传说中的老年代。
令我不解的是,每当说起Serial,他的眼神里有光,不是仇恨,而是着迷。他尊称Serial为回收者,而我总是背地里叫它帝国走狗,因为这他也没少说我。
听他说,Serial的年纪已经很大了,几乎在帝国诞生的时候就已经存在了,是GC家族中的一员,他个性孤僻,喜欢独来独往,但足够强悍和高效,一个线程就可以完成新生代的垃圾收集,因此一直工作到今天。
但是Serial乖张的性格让帝国的很多大臣不满,因为Serial在进行垃圾回收时,必须暂停其他所有的用户线程,直到他收集结束。这也是当时所有用户线程定在原地动弹不得的原因。
这就是大名鼎鼎的Stop The World。
时间过得飞快,我在Survivor From和Survivor To中辗转了几次,现在的年龄已经是0100了。
在这几次轮回中我失去了小美,和我聊天的大哥最终也没完成去老年代的心愿,但是他在被回收时倒是坦然,终于和自己的偶像近距离接触了。
Serial一如既往地摆着臭脸,一副正气凛然的样子。
我果然还是无法喜欢这些走狗!
第4回 进入老年代
再一次发生Minor GC了,但这次有点不一样。我没有再被转移到Survivor To区域,反而来到了一个新地方。
“欢迎来到老年代,年轻人。”一个柔和但饱经风霜的声音从我旁边传来。看到他头上的1111,我确定我来到了传说中的老年代,他这年纪我都得叫他大叔了。
“这里可是无数对象梦寐以求的地方,你怎么有点不开心。”大叔看出了我的情绪。
我能说我喜欢年轻的,不想在老年代里找对象吗?肯定不能啊。于是转个话题,“我明明没到岁数,怎么就来到老年代了呢?”
“看你这身形和年龄,我猜测可能是因为Survivor区的空间已经容不下Eden区和Survivor From回收之后的对象了,导致你们这些幸运儿提前进入了老年代。”
“这是一种幸运?”我不解。
“当然了,你看我是熬过了所有的Minor GC才来到这里,个中滋味只有我能体会。来到这里的好处就是帝国默认你是有用的对象,一般情况下不会轻易再被回收。”
或许确实因为GC压力比较小的原因,我看到老年代里的对象确实比新生代的对象懒散得多。我的目光聚焦在一个胖子身上,他身形巨大,但是年龄却只有0001。
“那个巨婴是怎么回事儿?”我指了指那个胖子。
“他啊”,大叔笑了笑,“因为他太胖了,伊甸园那点空间都容不下他,所以就干脆把他送到我们这里了。”
我有点疑惑:“为什么Serial不进行先进行Minor GC,腾出更多的地方来试着安置他呢?”
“Serial是何等精明的老头,你想想如果进行了Minor GC之后还是无法安置巨婴,那岂不是白白进行了一次Minor GC,要知道Minor GC是要Stop The World的啊。退一步讲,如果Minor GC之后真的能安置下这个巨婴,少不了以后在两个Survivor区域中来回扛着他走,Serial一把老骨头了,可不想做这种蠢事。”大叔解释道。
“唉,他也是个可怜的孩子,刚出生就被咱主人刻上了那样的命运。”大叔叹了口气。
“咱可不能歧视胖子啊,他们可都是潜力股!”我觉得胖被定义成命运有点过分了,于是反驳了一句。
“你还年轻,等你活的像我一样长,你自然会了解很多事儿。那个巨婴不只是胖,还很虚弱,你看他即使是坐着都大口喘个不停。他其实是一个虚引用对象(Weak reference Object)。”
看我一脸懵,大叔给我继续给我解释:“也难怪你不理解,他们在帝国中确实不常见。你我以及绝大多数的对象都是强引用对象,我们都是被new出来的,但是除了我们,还有软、弱、虚三种引用对象。这三种对象比不了我们,我们被回收需要被GC判定为垃圾对象才可以,而软引用对象会在内存空间不足时被二次回收,虚引用对象更惨,每次GC都会被回收。那个巨婴,活不过下一次GC。”
大叔刚说完,整片区域突然报警声肆虐。
“他来了。”
第5回 CMS登场
CMS排场很大,身后跟着好几个线程。
我顾不上他的排场,这种人为刀俎我为鱼肉的感觉令我不爽。
只见他首先派出了一个初始标记线程,他一出来,用户线程马上被钉在了原地动弹不得。这个场面我知道,是Stop The World。
“听说初始标记是标记一下GC Roots能直接关联到的对象,速度特别快,所以Stop The World的时间特别短。”,“是啊是啊,我觉得特别厉害。”周围的对象纷纷议论。
真是受够了,一群猪居然在惊叹屠夫的刀有多快,真是群蠢货!
一回头,CMS已经完成了初始标记转而开始进行并发标记了,确实是快啊。我问大叔:“刚才他们说的GC Roots是什么啊?”
“你怎么连这都不知道?”大叔有点嫌弃地瞟了我一眼,“帝国是通过可达性分析算法来判定我们是不是垃圾,基本思路就是通过一系列被称为GC Roots的根对象作为起始节点。从这些节点开始根据引用关系向下搜索,如果GC Roots到对象不可达,这个对象就会被回收。”
我点了点头,示意明白了,“现在CMS在进行另一轮的标记,而且用户线程也在同时运行呢。”
“没错,这就是并发标记中的并发的含义,因为这一步花费的时间稍微有点长,为了减少停顿,CMS允许用户线程和回收线程并发执行,CMS可是出了名的低停顿回收者。”
我有点不理解,在标记垃圾的过程中还在运行用户线程,这不就等于打扫房间的同时还扔垃圾吗?这啥时候能打扫完啊。
不知道是不是大叔看出了我的心事,继续解释:“这也是为了做到低延迟而不得不做的妥协,并发标记过程肯定会有新的垃圾对象产生,所以CMS还会进行第3个步骤,重新标。。。”
噗!大叔话还没说完,吐出了一大口鲜血。
我吓的一句话也说不出来,大叔擦了擦嘴边的血,安抚我说:“没事儿,就是突然感应到有个用户线程把我的引用置为null了,我命不久矣。”
我仿佛已经看到了大叔被钉在十字架上的样子,知道一个人的死期将至真是一件痛苦的事情,“那你要在这一轮GC中被带走了吗?”我尽量让我的措辞婉转一点。
大叔有气无力地笑了笑:“这一轮不会,因为我是在并发标记过程中被设置为null的,已经是在CMS标记之后了,所以我就成“浮动垃圾”,会在下一轮GC中被回收。CMS现在在进行他的第3个步骤,重新标记,你看,咳咳。。。。。又造成Stop The World了,这一步就是为了修正并发标记期间,因为用户线程继续运作而导致标记产生变化的那一部分对象的标记记录。”
大叔还想给我继续给我解释,让一个濒死之人向别人介绍自己的死亡方式未免过于残忍,我打断了他,让他好好休息。
一段时间之后,我亲眼看到大叔在下一轮CMS的并发清理中被回收了,被多个线程扛着走的场景还有点滑稽。
我,又从孤苦伶仃回到孑然一身。
第6回 难言之隐
往后的日子,我不停地和身边的伙伴交流,希望能更多地从他们的口中了解这个CMS,既然总有一死,也得死得明白。
我也大概摸索出了CMS的工作规律,每当老年代的使用空间到达一个阈值的时候就会导致CMS进行垃圾回收。由于大叔这种“浮动垃圾”的存在,导致CMS不能像其他收集器一样,等到老年代几乎完全被占满了才进行回收,而是必须预留出一定的空间供自己在并行收集的过程中用户线程分配新对象使用。
所以选择阈值就是一个很麻烦的事情,阈值太小,CMS的触发频率就会变高,影响整体性能;阈值太高,又怕CMS运行期间预留的内存无法满足用户线程分配新对象的需求,从而导致“并发失败”(Concurrent Mode Failure),这时候就不得不求助Serial这个老家伙进行老年代的垃圾回收了。
CMS也挺不容易的。
这段日子我还听说了一个Full GC的概念,据说只有在特别危及的关头才会进行Full GC,一次Full GC将会对新生代、老年代以及元空间进行垃圾回收。
但是大部分GC家族的成员都不愿提及Full GC,觉得这是一件特别丢脸的事情。因为一旦发生了Full GC,就意味着垃圾收集的速度已经赶不上新对象分配的速度了,原因可能是效率不高,也可能是因为内存空间被他们搞得七零八落。CMS便是后者。
CMS的全称叫做Concurent Mark Sweep,并发标记清除,并发之前已经见识过了,至于标记清除,嘿嘿,必然会产生一个大麻烦——内存碎片。
瞧瞧CMS在内存空间上捅的窟窿!
CMS为了解决这个问题,会在Full GC的时候选择是否将对象压缩,也就是把存活下来的对象都挪到一个角落里,排队站好,为其他对象腾出空间。
这就意味着,CMS一定会有Full GC的行为。想到这里我就开心,好想看看CMS在Full GC时的尴尬表情。
第7回 轮到我了
在一天清晨,我们如往常一样列队欢迎他。
不知道怎么回事儿,这天我昏昏沉沉,完全打不起精神,居然站着睡着了。再等我睁开眼睛,发现我被标记了。
旁边同样被打了标记的对象对着CMS破口大骂:“回收我们让你们很爽是嘛?洗好脖子给老子等着吧,G1早晚革了你的命!”
于是我眼睁睁地看着CMS走到跟前,把那家伙扔了出去。
然后走到我身边。
就算之前再怎么做心里建设,现在还是怕的要死,没人能为死真正做好准备。
我竭力锁紧身体让自己别那么引人注目,尽管气喘吁吁,但我仍然压抑住自己的呼吸,CMS还是发现了我。
第8回 终结
CMS扼住我的喉咙,直接把我拎了起来,“要走了,还有什么要说的吗?”
回想我这一生,没谈过恋爱,没有长久的朋友,反而时不时需要忍受GC的折磨,终日提心吊胆,甚至还要眼睁睁地看着朋友被回收。
我冷笑道:“这个世界算不上美好,来了倒也不后悔,只是老子下辈子再也不来了!。”