我也来谈一谈Java GC

以前写c/c++的时候虽然也有shared_ptr这样的自动内存管理机制,但是它内部其实是通过引用计数的原理进行内存管理的,容易产生循环应用的问题,也没有什么实际意义上的内存收集器。和java的内存收集机制差别很大,所以一直对java的内存收集机制抱有很强的好奇心。

最近在看《深入理解 Java 虚拟机-Jvm 高级特性与最佳实践》,里面对java垃圾收集讲的挺不错的。然后再将书中没有讲透的知识在网上搜索了下,整理成了这篇博客,哪天一时半会记不起来的时候还能回来瞧一瞧。

GC Roots

在Java虚拟机中,存在自动内存管理和垃圾回收机制,能自动回收没有用的对象的内存。

那怎么去判定一个对象是否还有用呢?java中是通过可达性分析来判定的。

具体的做法就是从一系列被称作"GC Roots"的对象作为起始点,从这些对象开始通过引用关系进行搜索。当GC Roots到某个对象没有任何引用链相连,则证明此对象是不可用的,是不需要存活,可以被清理的。

例如下图的object1、object2、object3、object4就是从GC Roots可达的,不能被回收。而object5、object6、object7虽然相互引用,但从GC Roots不可达,证明程序中无法访问到它们,所以它们是无用的,可以被回收。

1.png

在Java中,可以作为GC Roots的对象包括下面几种:

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

Java 堆中的内存分配与回收

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。

由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为新生代和老年代。

新生代中的Minor GC

大部分对象被创建时,内存的分配首先发生在年轻代(占用内存比较大的对象如数组,会被直接分配到老年带)。大部分的对象在创建之后很快就不再使用了,因此很快变成不可达的状态,于是被新生代的GC机制清理掉。这个GC机制被称为Minor GC或叫Young GC。

新生代可以分为3个区域:一个Eden区和两个Survivor区。两个Survivor中总有一个是空的,我们叫他Survivor To区,还有一个非空的,我们叫他Survivor From区。Eden区和两个Survivor区的大小为8:1:1。

对象被创建的时候,绝大部分都是被分配在Eden区。Eden区是连续的内存空间,因此在其上分配内存极快。当Eden区满的时候,就会执行Minor GC

复制算法

Minor GC时Eden区和Survivor From区还存活着的对象会一次性被复制到Survivor To区。然后Eden区和Survivor From区的内存会被清空。

之后原来的Survivor From区就空了,而原来的Survivor To区就非空。这个时候它们的角色就调换了,空的叫做Survivor To区,非空的叫做Survivor From区。

这种垃圾收集算法叫做复制算法,整个过程如下图所示:

2.png

为什么需要两个Survivor区

复制算法的优点在于,gc完成之后占用的内存会被整理到一个连续的空间中。而空闲的内存也是连续的区域,不会造成内存碎片。

如果只有一个Eden区和一个Survivor区,在Minor GC的时候,Eden区的存活对象可以被复制到Survivor区,这样Eden区可以被清空出一个完整的空闲内存区域。

而Survivor区存活的对象要怎么办呢:

  • 如果直接进入老年代。可能有些对象经过少数的几次GC就能被释放。但在老年代中GC发生的频率比新生代低很多。这样的话就会导致老年代的内存很快被占满。

  • 如果不管存活的对象,只是简单的清除不可达的对象。那么Survivor区就会产生内存碎片

  • 如果进行压缩整理,与从新生代复制过来的存活对象一起整理到Survivor中某个连续的区域的话,消耗的计算资源会比较高。

所以最简单的做法就是拿多一个Survivor To区出来,Eden区和Survivor From区存活的对象会被连续的复制到Survivor To区的一个连续区域中。然后将Eden区和Survivor From区清空就好。

由于新生代大部分的对象生命周期都很短,所以需要复制的对象的数目也不会很多,所以这是比较高效的做法。

对象进入老年代

对象在下面三种情况下,对象进入到老年代:

  • 占用内存比较大的对象如数组,在创建的时候不会分配到Eden区,而会被直接分配到老年代

  • 当Minor GC时Survivor To区已经放不下还存活的对象的时候,这些对象就会被放到老年代中。

  • 每经历一次Minor GC,对象的年龄会大一岁。当对象的年龄到达某一个值,Minor GC的时候就不会去到Survivor To区,而会进入老年代。

老年代

在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,所以选用复制算法,只需要付出少量对象的复制成本就能完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就需要使用"标记-清理"或者"标记-整理"算法来进行回收。

标记-清理算法

标记-清除算法顾名思义,主要就是两个动作,一个是标记,另一个就是清除。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

它的缺点有两个

  1. 标记与清除的效率都比较低
  2. 标记清除之后会产生大量不连续的内存碎片

它的执行过程如下图所示:

3.png

发生在老年代的GC叫做Major GC,通常当Minor GC晋升到老年代的大小大于老年代的剩余空间时,Major GC就会被触发。

出现了Major GC,通常会伴随着至少一次的Minor GC(不是绝对的,有些收集器有直接进行Major GC的策略)。Major GC的速度一般会比Minor GC慢10倍以上。

除了Minor GC和Major GC之外,还有一个Full GC的概念,它们的区别如下:

  1. Minor GC : 回收新生代的垃圾
  2. Major GC : 回收老年代的垃圾
  3. Full GC : 回收整个堆的垃圾,包括新生代、老年代、持久代等

标记-整理算法

标记过程仍和标记-清理算法一样,但是后续的步骤不是直接对可回收的对象进行清理,而是让所有存活的对象往一端移动,然后再清理掉边界以外的内存。它的过程如下图所示:

4.png

垃圾搜集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,所以不同的厂商、不同版本的虚拟机提供的垃圾收集器都可能有很大差别。

下面这张图列出了JDK1.7 Update 14之后HotSpot虚拟机所包含的垃圾收集器:

5.png

每种收集器具体的实现方法这里我就不罗列了,感兴趣的同学可以自行搜索。

finalize方法

即使在可达性分析中不可达的对象,也并非是"非死不可"的。这时它们只是处于"缓刑"阶段,要宣布一个对象死亡,至少要经历两次标记过程:

  1. 第一次可达性分析之后,不可达的对象会被标记出来放到一个"即将回收"的集合中。

  2. 被标记的对象会进行一次筛选,覆盖了finalize方法并且finalize方法没有被调用过的对象会放到一个叫做F-Queue的队列中。虚拟机会创建一个低优先级的Finalizer线程去执行它。如果一个对象想逃脱死亡的命运,只需要在finalize方法中将自己重新连接上引用链就可以了,例如将自己赋给某个类变量或对象的成员变量。

  3. 第二次可达性分析会将被重新连接上引用链的对象移出"即将回收"的集合。

  4. 最后将不可达的对象内存回收

这里有两点需要注意的是:

  • "执行"finalize方法指的是虚拟机会触发这个方法,但不承诺等待它运行结束,这样做的原因是防止某个对象的finalize方法运行缓慢或者发生死循环,导致F-Queue的队列其他对象永久等待甚至导致内存回收系统崩溃。

  • finalize只有一次被调用的机会。如果在finalize中将对象重新连接上引用链,只有在对象在第一次即将被回收的时候,finalize方法会被调用。在下一次GC的标记过程中,因为finalize方法已经被调用过了,所以就不会被放到F-Queue的队列中。

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

推荐阅读更多精彩内容